一道数组指针题搞清楚x86内存

文章也同时在简书更新
参考: iOS 最详细的解析(数组与指针)笔试题,并做了改进。

引题

先来看一下这道题目,如下代码的输出结果是什么?

1
2
3
short arrayName[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *q = (int *)(&arrayName+1);
printf("*(q-2):%d\n", *(q-2));

在看后文的解释前,不妨自己思考一下。

好了,相信你已经思考过了,我们来揭晓结果,结果是令人匪夷所思的458758,答对了吗?也许你会有疑惑,那么请看下面的注解,在其中增加了些许解释代码,并在关键代码后有注释。

详细注释解答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
short arrayName[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int i = 0; i < 10; i++) {
    printf("arr%d addr:%p\n", i, arrayName+i);
}
//&arratName是short(*)[10]类型
short *sp1 = (short *)&arrayName+2; //short(*)[10]先转short*,再移动2个short*位(4B)
short *sp2 = (short *)(&arrayName+2);//short(*)[10]先移动2个数组位(20个short*: 40B->0x28),再转short*型
printf("sp1:%p\nsp2:%p\n", sp1, sp2);
short *p = (short *)(&arrayName+1);//short(*)[10]先移动1个数组位(10个short*: 20B->0x14),再转short*型
int *q = (int *)(&arrayName+1);//short(*)[10]先移动1个数组位(10个short*: 20B->0x14),再转int*型
printf("p: %p\nq: %p\n", p, q);
printf("p-2:%p\nq-2:%p\n", p-2, q-2);
//q-2:移动2个short*(4B), p-2:移动2个int*(8B)
//x86是小端字节序: 0x0007 0006 -> 7*256*256+6=458758
printf("*(p-2):%d\n*(q-2):%d", *(p-2), *(q-2));

内存地址分配图:

内存地址分配图.png

重点:

  • 字节序
  • 内存地址分配
  • 类型转换

补充

最后我们通过汇编,来深入理解一下如下几行代码的含义:

1
2
3
int *p = NULL;
p = (int *)&arrayName;
p = (int *)(&arrayName+1);

对应的汇编分别为:

汇编代码.png

如图三个红圈,这边是AT&T的汇编格式,我们还是以更好看的intel的样式来说明:

  • 第一个红圈,左边的$0x00立即数赋给[rbp-0x28]这个内存地址,0x00就是NULL的值,而[rbp-0x28]内存地址就是指针p的值(不是*p)。
  • 第二个红圈,rcx寄存器中含有arrayName数组的首地址(注意:arrayName+0&arrayName取得的地址是相同的),赋给rdxrdx在把它赋给[rbp-0x28],即赋给指针p
  • 第三个红圈,第一句中,$0x14立即数就是十进制的20,表明10个short*型,因为每个short *占2B。$0x14赋给rcx寄存器,rcx再赋给指针p,最终导致前文所说的,所谓移动整个数组位,从第0个元素移到了第11个元素(虽然数组并没有第11个元素,但位置的移动可以这样理解)。

That’s all. Thanks for reading.

微信公众号

第一时间获取最新内容,欢迎关注微信公众号:「洛斯里克的大书库」。
微信公众号「洛斯里克的大书库」

周鶏🐣(Kimiko) wechat
拿起手机扫一扫,欢迎关注我的个人微信公众号:「洛斯里克的大书库」。
坚持原创技术分享,您的支持将鼓励我继续创作!