您的位置:首页 > 其它

"显微镜"下细看字符串常量初始化数组和指针

2011-05-15 16:59 399 查看
先看代码:

1:  #include "stdafx.h"

2:  #include <iostream>

3:

4:  using namespace std;

5:

6:  int main(int argc, char** argv)

7:  {

8:      char * p = "ABCD";

9:      char a[] = "ABCD";

10:      return 0;

11:  }


很多学习C语言的码农们,对于通过字符串常量形式初始化数组和指针,在头脑里没有一个清晰的概念,有很多人认为这两者是等同的,那么这两种到底有什么区别呢,本文通过使用VC6.0下的"显微镜" 反编译器Disassembly来仔细的看看隐藏的秘密^_^。

要使用反编译器,我们首先在程序第一条语句前设置断点(什么,不知道怎么设断点,莫非你是火星来的programmer!F9设置和取消断点,F10单行调试语句,F11能进入函数子程序进行调试,CTRL+F10 运行到鼠标指定的行,shift+F11跳出当前函数子程序),然后我们按F5调试程序,程序会在第一个设置断点的地方停下来。当然如果你想从程序的第一条语句开始调试也可以,直接按F10就OK啦。

那么我怎么怎么调出我们的显微镜呢,只需要View->Debug Window->Disassembly就可以看到我们的程序的编译器产生的代码啦。如下:

10: {

00401250 push ebp

00401251 mov ebp,esp

00401253 sub esp,4Ch

00401256 push ebx

00401257 push esi

00401258 push edi

00401259 lea edi,[ebp-4Ch]

0040125C mov ecx,13h

00401261 mov eax,0CCCCCCCCh

00401266 rep stos dword ptr [edi]

11: char * p = "ABCD";

00401268 mov dword ptr [ebp-4],offset string "ABCD" (0043101c)

12: char a[] = "ABCD";

0040126F mov eax,[string "ABCD" (0043101c)]

00401274 mov dword ptr [ebp-0Ch],eax

00401277 mov cl,byte ptr [string "ABCD"+4 (00431020)]

0040127D mov byte ptr [ebp-8],cl

13: return 0;

00401280 xor eax,eax

14: }

从汇编代码我们可以得出以下一些结论:

1.变量名只在我们编写代码和编译器编译的时候用到,一旦编译过后,生成的代码中就不再有变量名的概念了,因为变量已经实实在在的被变量的地址给取代了(变量本不存在,地址取代之)。可通过具体编译代码获知此结论。

mov dword ptr [ebp-4],offset string "ABCD" (0043101c)

上面这句话将字符串常量“ABCD”的地址(offset) 赋值给地址dword ptr [ebp-4](这就是p的地址),同时表明指针变量内存大小为4个字节。

2.字符串常量"ABCD"的地址是唯一的,因为两个初始化中string "ABCD" (0043101c),表明字符串常量存储在一个固定的地方,实际上字符串常量常常存储在只允许读的数据段中rodata中,以防止它被修改。

3.通过字符串常量初始化数组中:mov eax,[string "ABCD" (0043101c)] 这句汇编代码将地址0043101c的内容([地址]表示地址中的内容)赋值给eax,因为eax为4个字节,所以刚好将A、B、C、D四个字符存储到寄存器eax中。dword ptr [ebp-0Ch],eax。这句代码将寄存器中的四个字节存放到地址 ebp-0Ch中,实际上就是数组变量a中,只是经过编译变量名已经不存在了。mov cl,byte ptr [string "ABCD"+4 (00431020)] 这叫将"ABCD"+4 (00431020)地址的内容,即字符串最后一个字节内容'/0',存放在单字节寄存器cl中。 mov byte ptr [ebp-8],cl 这里表示将寄存器中的内容存放在内存字节地址ebp-8中。

4. 常量字符串在内存中通过一个固定地址来标识: string "ABCD" (0043101c) 表示字符串"ABCD"的地址0043101c。

上面只是简单的解释了汇编代码,下面我们通过内存空间来窥探为什么会有ebp-4、ebp-0Ch、ebp-8这些奇怪的地址。下面是我用visio画的内存示意图:




图一:内存示意图
通过View->Debug Window->Memery可以掉出内存分布图,可以看出0x0043101Ch存放的恰好是ABCD/0





图二:Memery示意图
通过View->Debug Window->Registers掉出寄存器视图,可以知道EBP的地址为0x0012FF80



图三:寄存器示意图
我们查看0x0012FF80-0C的地址内容,这个地址从低到高正是我们的数组a和p的内容





图四:Memery示意图
图中需要注意的地方如下:

1.栈的扩展是从高地址向地址方向,ebp为栈基地址寄存器(指向栈底),esp为栈偏移地址寄存器(指向栈顶),图中ebp-4右边的箭头表示ebp-4指向的地址,汇编代码中的[ebp-4]表示ebp-4指向的内容,即我们被地址取代的变量p=[ebp-4].

2.虽然栈的扩展方向是由高到低,但是变量的存储却按照字节序,intel处理机上字节序为little-endian小字节序, 即小地址存储低字节,我们的图就是选择的小字节序,所以将p = 0x0043101C 后,ebp-4低地址存放的是低字节0x1C,由低地址到高地址,依次按照0x1C,0x10,0x43,0x00存放。这里还有需要注意的一点事,变量的地址总是指它的低地址,所以p的地址是ebp-4,而不是ebp-1.同理数组内容也是数组前面的内容存放在低地址,数组后面的内容存放在高地址。

3.为什么a的地址是ebp-0Ch,不应该是ebp-9吗,这里就是编译器采取了优化存取方式的字节对齐aligement方法,使a的地址为ebp-0Ch = ebp – 12 。

4.可以看到char * p = "ABCD";只是将静态字符串常量的地址赋给了指针变量p,而char a[] = "ABCD";是将静态常量字符串的内容完完整整的拷贝了一份到栈中。

好了,对于字符串常量初始化数组和指针的问题,我们就讨论在这里,这是第一次用微软的live writter写的博客,其中的代码可以通过添加代码插入插件实现(这个以前看到人家的博客上总是可以出现好多的漂亮的代码,羡慕不已)。欢迎大家进行指导。相互讨论才能获得更深的领悟。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐