通过汇编分析数组名与指向数组的指针

数组名是指向数组的指针吗

记得学习C语言的时候,有这样一个说法"数组名实际就是一个指向数组首地址的指针常量",
那么对数组名取地址和对数组第1个元素取地址 必然得到相同结果

char p2[20];
printf("p2: %08x, p2[0]: %08x, p2[19]: %08x\n", (int)&p2, (int)&p2[0], (int)&p2[19]);
// 本机输出 p2: 0019ff18, p2[0]: 0019ff18, p2[19]: 0019ff2b

可以看到&p2 == &p2[0]

那指向数组的指针跟数组名的使用一样吗

由于数组还可以通过new来得到,char* p = new char[20],p是一个指向数组的指针,既然p和p2都是指向数组的指针是否意味着已知&p2 == &p2[0] 一定有 &p == &p[0]呢?

    char * p = new char[20];
    printf("p: %08x, p[0]: %08x, p[19]: %08x\n", (int)&p, (int)&p[0]);
    
    char p2[20];
    printf("p2: %08x, p2[0]: %08x, p2[19]: %08x\n", (int)&p2, (int)&p2[0], (int)&p2[19]);
    // 本机输出
    // p: 0019ff2c, p[0]: 007e49e0, p[19]: 004010f0
    // p2: 0019ff18, p2[0]: 0019ff18, p2[19]: 0019ff2b

可以看到&p2 == &p2[0] 但是 &p != &p[0]

来看看汇编里都干了啥

为什么会这样呢?我们修改一下代码看一下汇编输出

这里涉及到两条汇编指令lea 和 mov 来看一下wikibook上的解释

  • mov stands for move. Despite its name the mov instruction copies the src operand into the dest operand. After the operation both operands contain the same contents.
  • lea stands for load effective address. The lea instruction calculates the address of the src operand and loads it into the dest operand.
  • Load Effective Address calculates its src operand in the same way as the mov instruction does, but rather than loading the contents of that address into the dest operand, it loads the address itself. lea和mov用同样的方法计算操作数,但lea将计算出的地址存入目标,mov将该地址上的内容存入目标
6:    int main(int argc, char* argv[])
7:    {
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,60h
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-60h]
0040101C   mov         ecx,18h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
8:        char * p = new char[20];
00401028   push        14h
0040102A   call        operator new (00401090)
0040102F   add         esp,4
00401032   mov         dword ptr [ebp-20h],eax
00401035   mov         eax,dword ptr [ebp-20h]
00401038   mov         dword ptr [ebp-4],eax   // p本身是一个变量在栈上,它的地址&p是也一个数值,这个数值在ebp-4,   new出来数组的地址在堆上,数组的地址需要放在变量本身占据的那块内存dword ptr [ebp-4]中
9:        int tmp = (int)&p;
0040103B   lea         ecx,[ebp-4]  // lea取的是 ebp-4的变量p的地址值
0040103E   mov         dword ptr [ebp-8],ecx
10:       tmp = (int)&p[0];
00401041   mov         edx,dword ptr [ebp-4] // mov取的是[ebp-4]指向存储单元中的内容, 这里编译器识别出p[0]是数组第1个元素,取的是变量p指向数组的地址
00401044   mov         dword ptr [ebp-8],edx
11:       p[0] = 3;
00401047   mov         eax,dword ptr [ebp-4]  // 在给p[0] 赋值时先得到真正的堆上的地址值,再将数据存入该地址空间
0040104A   mov         byte ptr [eax],3
12:       //printf("p: %08x, p[0]: %08x, p[19]: %08x\n", (int)&p, (int)&p[0]);
13:
14:       char p2[20];
15:       tmp = (int)&p2;
0040104D   lea         ecx,[ebp-1Ch]   // p2在栈上 p2本身的地址值在ebp-1ch 它本身的地址值就是数组的地址
00401050   mov         dword ptr [ebp-8],ecx
16:       tmp = (int)&p2[0];
00401053   lea         edx,[ebp-1Ch]   // 同样
00401056   mov         dword ptr [ebp-8],edx
17:       p2[0] = 3;
00401059   mov         byte ptr [ebp-1Ch],3      // 在给p2[0]赋值时 直接使用地值值即可,注意与p[0]=3的区别
18:       //printf("p2: %08x, p2[0]: %08x, p2[19]: %08x\n", (int)&p2, (int)&p2[0], (int)&p2[19]);
19:       return 0;
0040105D   xor         eax,eax
20:   }

结果

p和p2虽然都可以理解为指向数组的指针,但还是有细微区别的,它们的内存分别在栈上和堆上
通过上面分析可知,取数组地址时统一使用&p[0] 和 &p2[0] 这种方式是不会出错的。&p和&p2则有可能不是你想要的结果。

你可能感兴趣的:(C++,c++,开发语言)