取消引用指针和访问数组元素之间的区别
Posted
技术标签:
【中文标题】取消引用指针和访问数组元素之间的区别【英文标题】:Difference between dereferencing pointer and accessing array elements 【发布时间】:2014-03-25 05:06:19 【问题描述】:我记得一个例子,其中演示了指针和数组之间的区别。
当作为函数参数传递时,数组衰减为指向数组中第一个元素的指针,但它们并不等价,如下所示:
//file file1.c
int a[2] = 800, 801;
int b[2] = 100, 101;
//file file2.c
extern int a[2];
// here b is declared as pointer,
// although the external unit defines it as an array
extern int *b;
int main()
int x1, x2;
x1 = a[1]; // ok
x2 = b[1]; // crash at runtime
return 0;
链接器不对外部变量进行类型检查,因此在编译时不会产生错误。问题是 b
实际上是一个数组,但是编译单元 file2
没有意识到这一点,并将 b
视为指针,导致尝试取消引用时崩溃。
我记得当时解释得很有道理,但现在我不记得解释了,我也不能自己想出来。
所以我想问题是在访问元素时,数组与指针的处理方式有何不同? (因为我认为p[1]
被转换为(相当于)*(p + 1)
的程序集,无论p
是一个数组还是一个指针——我显然错了)。
两次解引用(VS 2013)生成的程序集:注意:1158000h
和1158008h
分别是a
和b
的内存地址
12: x1 = a[1];
0115139E mov eax,4
011513A3 shl eax,0
011513A6 mov ecx,dword ptr [eax+1158000h]
011513AC mov dword ptr [x1],ecx
13: x2 = b[1];
011513AF mov eax,4
011513B4 shl eax,0
011513B7 mov ecx,dword ptr ds:[1158008h]
011513BD mov edx,dword ptr [ecx+eax]
011513C0 mov dword ptr [x2],edx
【问题讨论】:
我可以解释所有细节,但我不能做得比这更好。查看第 96 页 books.google.com.au/… 【参考方案1】:感谢@tesseract 在 cmets 中提供的链接:Expert C Programming: Deep C Secrets(第 96 页),我想出了一个简单的答案(书中解释的简单愚蠢版本;要获得完整的学术答案,请阅读书):
当声明int a[2]
时:
编译器为a
提供了一个存储此变量的地址。这个地址也是数组的地址,因为变量的类型是数组。
访问a[1]
意味着:
正在检索该地址,
添加偏移量和
访问此计算出的新地址处的内存。
当声明int *b
时:
编译器还有一个b
的地址,但这是指针变量的地址,而不是数组。
所以访问b[1]
意味着:
正在检索该地址,
访问该位置以获取b
的值,即数组的地址
向该地址添加偏移量,然后
访问最终内存位置。
【讨论】:
【参考方案2】:// in file2.c
extern int *b; // b is declared as a pointer to an integer
// in file1.c
int b[2] = 100, 101; // b is defined and initialized as an array of 2 ints
链接器将它们链接到相同的内存地址,但是由于符号b
在file1.c
和file2.c
中具有不同的类型,因此对相同的内存位置的解释不同。
// in file2.c
int x2; // assuming sizeof(int) == 4
x2 = b[1]; // b[1] == *(b+1) == *(100 + 1) == *(104) --> segfault
b[1]
首先被评估为*(b+1)
。这意味着获取b
绑定的内存位置的值,将1
添加到它(指针算法)以获得新地址,将该值加载到CPU寄存器中,将该值存储在x2
的位置是势必。所以b
绑定的位置的值是100
,加上1
得到104
(指针运算;sizeof *b
是4),得到地址104
的值!这是错误且未定义的行为,很可能会导致程序崩溃。
访问数组元素的方式和访问指针指向的值的方式是不同的。举个例子吧。
int a[] = 100, 800;
int *b = a;
a
是一个由2
整数组成的数组,b
是一个指向整数的指针,该整数初始化为a
的第一个元素的地址。现在,当访问a[1]
时,这意味着从a[0]
的地址(即符号a
绑定到的地址(和下一个块))获取偏移量1
处的任何内容。这是一个汇编指令。就好像一些信息被嵌入到数组符号中,以便 CPU 可以在一条指令中获取距数组基地址偏移的元素。当您访问*b
或b[0]
或b[1]
时,您首先获取b
的内容,这是一个地址,然后进行指针运算以获取新地址,然后获取该地址处的任何内容。所以 CPU 必须首先加载b
的内容,评估b+1
(对于b[1]
),然后加载地址b+1
的内容。这是两个汇编指令。
对于外部数组,你不需要指定它的大小。唯一的要求是它必须与它的外部定义相匹配。因此以下两个语句是等价的:
extern int a[2]; // equivalent to the below statement
extern int a[];
您必须将其声明中的变量类型与其外部定义相匹配。解析符号引用时,链接器不检查变量类型。只有函数具有编码到函数名称中的函数类型。因此,您不会收到任何警告或错误,并且可以正常编译。
从技术上讲,链接器或某些编译器组件可以跟踪符号所代表的类型,然后给出错误或警告。但是标准没有要求这样做。你必须做正确的事。
【讨论】:
为什么当你声明extern时会发生这种情况,但如果你在同一个文件上声明就不会?例如,为什么 "int a[2] = 100,200; int *b = a; printf("b[1] = %d\n",b[1]);"工作正常吗? @Cantfindname 那是因为a
和b
是两个不同的变量。它们绑定到不同的内存地址。但是,extern
变量都链接到相同的内存位置。它们就像两个不同文件范围内相同内存位置的别名。
请注意区分兼容和等效定义。【参考方案3】:
这并不能完全回答您的问题,但它会提示您正在发生的事情。稍微修改你的代码给
//file1.c
int a[2] = 800, 801;
int b[2] = 255, 255;
#include <stdio.h>
extern int a[2];
// here b is declared as pointer,
// although the external unit declares it as an array
extern int *b;
int *c;
int main()
int x1, x2;
x1 = a[1]; // ok
c = b;
printf("allocated x1 OK\n");
printf("a is %p\n", a);
printf("c is %p\n", c);
x2 = *(c+1);
printf("%d %d\n", x1, x2);
return 0;
现在,当您运行它时,您仍然会遇到段错误。但就在您这样做之前,您会了解原因:
allocated x1 OK
a is 0x10afa4018
c is 0xff000000ff
Segmentation fault: 11
指针 c 的值不是您所期望的:它似乎不是指向数组 b
开头的指针(这将是靠近 a
的合理内存位置),它似乎包含 数组 b 的内容...(当然,0xff
是十六进制的 255
)。
我不能很清楚地解释为什么会这样 - 为此,请参阅 @tesseract 在 cmets 中给出的 link(实际上第 4 章的所有内容都非常有用)。
【讨论】:
以上是关于取消引用指针和访问数组元素之间的区别的主要内容,如果未能解决你的问题,请参考以下文章