回 帖 发 新 帖 刷新版面

主题:[原创]理解C++ — 变量与常量(2)

变量与常量(2)

上接变量与常量(1)

前面已经说过了, const这个关键字可以施加一种约束, 使你不会错改内存的值。不过要是你真的想改变那个值,还是可以的, 麻烦一点而已。比如
================================
int a = 2;
const int* pa = &a;
//*pa = 100;                  Error
*const_cast<int*>(pa) = 100; //OK
===============================
*pa = 100 错误是因为我们用const施加一种约束。而const_cast是个转型符,用来将const约束取消掉,故*const_cast<int*>(pa) = 100可以通过。const_cast只能用在指针和引用类型。

为什么C++会设计这样一个转型符呢?是为了给程序员更大的自由。C++的一个设计理念是信任程序员,它假设程序员会知道自己在干什么。当你写出*pa = 100的时候,你可能是不小心。但是当你写出*const_cast<int*>(pa) = 100的时候就代表你清楚自己想做什么了。C++是自由的,但是为了享受这份自由,你会经历很大磨难。而其它很多语言,当自己是保姆,当程序员是小孩子,将很多东西都包装好,这个不准那个也不准。以学自行车为例,C++会放开双手,让你自己去骑,开头当然会跌得很惨,之后你可以骑车到你想去的任何地方;另外一些语言就不同了,在车子后面安装两个小轮子,还不放心,再在前面安装两个,没错是很安全,不过骑不快,遇到窄点的地方就进不去了。C++的自由是争议很大的地方,各人都有不同的观点。

(注: 想了解多点转型符,可以参考More Effective C++条款2, 本文主要讲些基本概念,不会讲太多语法)

那么到底那些常量变量,静态动态,是怎么在内存是怎么布置的呢?

我们知道,数据有不同的性质,有些可读不可写,有些可读可写,有些只可以给系统读写......等等。这样不同性质的数据就分开放到内存不同的位置,这叫分段或者叫分区。分段之后最大的好处是容易实现保护,可以指定从这里开始,到那里结束,这个范围的内存空间的性质,假设是只读的,要是程序执行的时候意图修改,就会触发错误处理。我们经常会将空指针赋值为0, 低端地址通常为系统保留,不可被访问,当想引用地址0中的数据,就会出错。原则上将空指针赋值为不可访问的内存地址就可以了,不一定是0。
============================================
     高地址 +-----------+
            |           | (未初始化)变量
            +-----------+
            |           | 静态变量
            +-----------+
            |           | 只读
            +-----------+ 
            |           | 常量(不可访问)
            +-----------+
            |           | 代码 
            +-----------+  
            |           | 堆 
            +-----------+  
            |           | 栈  
            +-----------+  
            |           | 系统保留 
     低地址 +-----------+
        windows程序执行时的典型内存分区
==========================================
段是按照数据的性质而不是按用途来分的,常量与代码就可以合为一段。为了更快速地将程序装入内存,通常可执行文件也会有一定的结构,内部也可以分区,称为节区。VC中有个小工具dumpbin, 你可以随便编译一个程序,比如main.exe, 敲入命令, dumpbin main.exe, 会出现类似的字样
========================================
Summary
        4000 .data
        1000 .rdata
        5000 .text
===================================
这些.data .rdata .text是节区的名称。程序定义的一些常量,比如字符串,多数会放到.data里面。当程序被执行时,这些内容会被装到内存的相应位置。想看看节区里面的内容,可以敲入 dumpbin main.exe -section:.data -rawdata:bytes > data.txt 之类的命令。如果学过汇编的朋友,这些应该会很清楚,有那些.data .code .const之类的语句。

下面,介绍一下栈(stack)。学过数据结构的都应该知道,stack是先进后出的。比如叠起来的碟子,只可以在最上面放和最方面取。当放上一个碟子,就会变高。当取下一个碟子,就会变低。最先放的碟子会在最后取出,最后放的会最先取出。放碟子,使碟子升高,这动作叫做push, 取碟子,使碟子降低,这动作叫做pop。

stack这结构用得极其频繁,8086系列的计算机就有相应的机械指令push, pop和相应的寄存器(e)sp (e)bp来支持硬件上的栈。

为得到数据,需要得到它的地址。通常地址都不会直接给出的,会使用基址+偏移的形式。这种形式可以表述为 从某某地方开始,向下或者向上数多少格。所谓基址是一个参考点,偏移是相对参考点而言的。比如 基址为1000, 偏移为5,就得到1005; 基址为1000, 偏移为-5, 就得到995。基址+偏移的形式也用的很广泛,平时接触得最多的算是数组了。

寄存器ebp就表示基址, 通常我们叫它栈底寄存器,其实不用理它叫什么,知道是个参考点就行了。esp就是一个偏移, 相对ebp而言, 指示栈的顶端。push指令会使esp减少(减少意味着距离ebp更远,就是栈顶升高), 跟着在那个地方放数据。pop指令将eip所指的数取出,更着eip增加(增加意味着距离ebp近了,就是栈顶降低)。至于升高或降低多少,就看你的数据有多大了。
=============================================
push eax

    EAX      | .....   |      EAX      | .....   |       EAX      | .....   |
  12345678H  +---------+    12345678H  +---------+     12345678H  +---------+
   ESP 0     |  02h    |     ESP -4    |  02h    |      ESP -4    |  02h    |
             +---------+               +---------+                +---------+
             |  0Dh    |          ESP->|  0Dh    |           ESP->|  78h    |
             +---------+               +---------+                +---------+
             |  10h    |               |  10h    |                |  56h    |
             +---------+               +---------+                +---------+
             |  F0h    |               |  F0h    |                |  34h    |
             +---------+               +---------+                +---------+
             |  06h    |               |  06h    |                |  12h    |
             +---------+               +---------+                +---------+
   EBP(ESP)->|  78h    |          EBP->|  78h    |           EBP->|  78h    |
             +---------+               +---------+                +---------+
             | ......  |               | ......  |                | ......  |
             |         |               |         |                |         |
                 开始                    中间过程                   之后
=========================================
pop ebx
 
     EBX      | .....   |      EBX      | .....   |       EBX      | .....   |
   00000000H  +---------+    12345678H  +---------+     12345678H  +---------+
    ESP -4    |  02h    |     ESP -4    |  02h    |      ESP 0     |  02h    |
              +---------+               +---------+                +---------+
         ESP->|  78h    |          ESP->|  78h    |                |  78h    |
              +---------+               +---------+                +---------+
              |  56h    |               |  56h    |                |  56h    |
              +---------+               +---------+                +---------+
              |  34h    |               |  34h    |                |  34h    |
              +---------+               +---------+                +---------+
              |  12h    |               |  12h    |                |  12h    |
              +---------+               +---------+                +---------+
         EBP->|  78h    |          EBP->|  78h    |      EBP(ESP)->|  78h    |
              +---------+               +---------+                +---------+
              | ......  |               | ......  |                | ......  |
              |         |               |         |                |         |
                 开始                    中间过程                    之后
===========================================
(注1:发觉自己真的很罗嗦,希望不会使大家很烦。这帖子是针对初学者的,所以有些很简单的东西也写进去了,不是看不起大家哦)
(注2:写程序时用的地址只是线性地址,从线性地址到物理地址还有个令人抓狂的复杂过程)
(注3:本来这主题打算只写一篇帖子的,发觉实在太长。)

回复列表 (共10个回复)

沙发

顶了~~~LZ辛苦了~~

板凳

楼主发表了这么多篇的精彩文章,真是辛苦了。

不过,可以把所有的资料打包后再上传供网友下载。

3 楼

顶,暴lz大名!!!

4 楼

之前犯了个大错误,将寄存器ESP, 写成EIP。已经更正,请原谅

5 楼

楼主是不是师范院校的啊,简单点没事,关键是言之有物

6 楼

q bn go sfdm ..............

7 楼

我看过了,,,,,,,,,,,,,

8 楼

好帖,顶!!

9 楼

[quote]楼主发表了这么多篇的精彩文章,真是辛苦了。

不过,可以把所有的资料打包后再上传供网友下载。[/quote]

10 楼

看来自己学得真的相当的肤浅啊,这个篇文章里面的东西还真的有点不懂,呵呵!!!我还要继续加油啊!谢谢楼主分享自己的经验!

我来回复

您尚未登录,请登录后再回复。点此登录或注册