我记得一个示例,其中演示了指针和数组之间的区别。
当作为函数参数传递时,数组衰减到指向数组中第一个元素的指针,但它们不等效,如下所示:
//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>
我记得当这被解释时它很有道理,但现在我不记得解释,我也不能自己解决。
所以我想问题是在访问元素时,数组与处理指针的处理方式有何不同? (因为无论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
答案 0 :(得分:15)
感谢@tesseract在评论中提供的链接:Expert C Programming: Deep C Secrets(第96页),我想出了一个简单的答案(书中解释的简单愚蠢版本;完整的学术答案读过这本书):
int a[2]
时a
存储了此变量的地址。该地址也是数组的地址,因为变量的类型是数组。a[1]
表示:
int *b
时b
的地址,但这是指针变量的地址,而不是数组。b[1]
意味着:
b
的值,即数组的地址答案 1 :(得分:11)
// 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]
时,它意味着从1
的地址获取偏移a[0]
处的任何内容,即符号a
的地址(和下一个块)受约束。这是一个汇编指令。就好像某些信息嵌入到数组符号中一样,这样CPU就可以在一条指令中从数组的基址偏移一个元素。当您访问*b
或b[0]
或b[1]
时,首先获取b
的内容,这是一个地址,然后执行指针算术以获取新地址,然后获取无论那个地址在那里。因此,CPU必须首先加载b
的内容,评估b+1
(对于b[1]
),然后在地址b+1
加载内容。这是两个装配说明。
对于extern数组,您不需要指定其大小。唯一的要求是它必须与其外部定义匹配。因此,以下两个陈述都是等效的:
extern int a[2]; // equivalent to the below statement
extern int a[];
您必须将其声明中的变量类型与其外部定义相匹配。在解析符号引用时,链接器不检查变量的类型。只有函数具有编码到函数名称中的函数类型。因此,您不会收到任何警告或错误,它会编译得很好。
从技术上讲,链接器或某些编译器组件可以跟踪符号所代表的类型,然后给出错误或警告。但是标准没有要求这样做。你需要做正确的事。
答案 2 :(得分:5)
这并没有完全回答你的问题,但它会给你一个暗示正在发生的事情。稍微修改你的代码以给出
//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给出的link(实际上第4章的内容非常有用)。