bol*_*lov 21 c arrays pointers dereference
我记得一个示例,其中演示了指针和数组之间的区别.
当作为函数参数传递时,数组衰减到指向数组中第一个元素的指针,但它们不等效,如下所示:
//file file1.c
int a[2] = {800, 801};
int b[2] = {100, 101};
Run Code Online (Sandbox Code Playgroud)
//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;
}
Run Code Online (Sandbox Code Playgroud)
链接器不会对外部变量进行类型检查,因此在编译时不会生成错误.问题是它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
Run Code Online (Sandbox Code Playgroud)
bol*_*lov 15
感谢@tesseract在评论中提供的链接:专家C编程:Deep C Secrets(第96页),我想出了一个简单的答案(书中解释的简单愚蠢版本;完整的学术答案阅读这本书):
int a[2]
:
a
存储此变量的地址.该地址也是数组的地址,因为变量的类型是数组.a[1]
手段:
int *b
:
b
但这是指针变量的地址,而不是数组.b[1]
意味着:
b
,即数组的地址aja*_*jay 12
// 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
Run Code Online (Sandbox Code Playgroud)
链接器将它们链接到相同的存储器地址,但是由于符号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
Run Code Online (Sandbox Code Playgroud)
b[1]
首先评估为*(b+1)
.这意味着获取内存位置b
绑定的值,添加1
到它(指针算术)以获取新地址,将该值加载到CPU寄存器中,将该值存储在该位置x2
绑定.所以,该位置的值b
绑定是100
,添加1
到它104
(指针算术; sizeof *b
是4)并获取地址的值104
!这是错误的和未定义的行为,很可能会导致程序崩溃.
如何访问数组元素以及如何访问指针指向的值有所不同.我们来举个例子吧.
int a[] = {100, 800};
int *b = a;
Run Code Online (Sandbox Code Playgroud)
a
是一个2
整数数组,b
是一个指向初始化为第一个元素地址的整数的指针a
.现在,当a[1]
被访问时,就意味着得到什么是有偏移量1
从地址a[0]
,地址(和下一个块),其中符号a
必然.这是一个汇编指令.就好像某些信息嵌入到数组符号中一样,这样CPU就可以在一条指令中从数组的基址偏移一个元素.当您访问*b
or b[0]
或者b[1]
,首先获取其内容b
是地址时,然后执行指针算法以获取新地址,然后获取该地址处的任何内容.因此,CPU必须首先加载内容b
,评估b+1
(for b[1]
)然后在地址加载内容b+1
.这是两个装配说明.
对于extern数组,您不需要指定其大小.唯一的要求是它必须与其外部定义匹配.因此,以下两个陈述是等效的:
extern int a[2]; // equivalent to the below statement
extern int a[];
Run Code Online (Sandbox Code Playgroud)
您必须将其声明中的变量类型与其外部定义相匹配.在解析符号引用时,链接器不检查变量的类型.只有函数具有编码到函数名称中的函数类型.因此,您不会得到任何警告或错误,它会编译得很好.
从技术上讲,链接器或某些编译器组件可以跟踪符号表示的类型,然后给出错误或警告.但是标准没有要求这样做.你需要做正确的事.
这并没有完全回答你的问题,但它会给你一个暗示正在发生的事情.稍微修改你的代码给予
//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;
}
Run Code Online (Sandbox Code Playgroud)
现在,当你运行它时,你仍然会遇到段错误.但就在您做之前,您可以深入了解原因:
allocated x1 OK
a is 0x10afa4018
c is 0xff000000ff
Segmentation fault: 11
Run Code Online (Sandbox Code Playgroud)
指针C的值是不是您所期望的:不是为指针数组的开始b
(这将是一个明智的存储位置接近a
),它似乎包含内容数组b ...(0xff
是255
十六进制,当然).
我无法清楚地解释为什么会这样 - 为此,请参阅评论中@tesseract给出的链接(实际上第4章的所有内容都非常有用).