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

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

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

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

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

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

由于数组还可以通过new来得到,char* p = new char[20],p是一个指向数组的指针char p2[20]p2也是一个指向数组的指针,既然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则有可能不是你想要的结果。
在栈上时p就代表数组首地址,&p也是数组首地址
在堆上的数组,p2代表指向数组首地址的指针,p2是在栈上,&p2 != 堆上数组的首地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nwao7890

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值