取消引用指针和访问数组元素之间的区别

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)生成的程序集:注意:1158000h1158008h分别是ab的内存地址

    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

链接器将它们链接到相同的内存地址,但是由于符号bfile1.cfile2.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 可以在一条指令中获取距数组基地址偏移的元素。当您访问*bb[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 那是因为ab 是两个不同的变量。它们绑定到不同的内存地址。但是,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 章的所有内容都非常有用)。

【讨论】:

以上是关于取消引用指针和访问数组元素之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

取消引用指向访问元素的向量指针

通过指针、强制转换和取消引用加载向量?

面试中常被问到C/C++中数组,指针和引用的区别

perl二维数组

(C/C++)区别:数组与指针,指针与引用

int * i 和 int** i 之间的区别