现在的位置: 首页 > 综合 > 正文

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

2013年08月13日 ⁄ 综合 ⁄ 共 3038字 ⁄ 字号 评论关闭

   先看代码:

   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

image

   图二:Memery示意图

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

 

image 图三:寄存器示意图

   我们查看0x0012FF80-0C的地址内容,这个地址从低到高正是我们的数组a和p的内容

image

图四: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写的博客,其中的代码可以通过添加代码插入插件实现(这个以前看到人家的博客上总是可以出现好多的漂亮的代码,羡慕不已)。欢迎大家进行指导。相互讨论才能获得更深的领悟。

 

抱歉!评论已关闭.