直接使用指向数组和数组地址的指针不一致

此代码示例正确打印数组。

int b[2] = {1, 2}; int *c = &b; int i, j,k = 0; for (i = 0;i < 2; i++) { printf("%d ", *(c+i)); } 

而这一个打印两个垃圾值。

 int b[2] = {1, 2}; int i, j,k = 0; for (i = 0;i < 2; i++) { printf("%d ", *(&b+i)); } 

为什么两个代码示例的行为不同?

声明:

 int b[2] = {1, 2}; 

创建一个两个int的数组,值为2
假设int的系统大小是4字节,那么数组b[]应该存储在内存中,如下所示:

 first ele +----------+ (b + 0) ---►| 1 | 0xbf5c787c <----- &b , (c + 0) next ele +----------+ (b + 1) ---►| 2 | 0xbf5c7880 <------------- (c + 1) +----------+ (b + 2) ---►| ? | 0xbf5c7884 <----- (&b + 1) next array +----------+ ---►| ? | 0xbf5c7888 +----------+ ---►| ? | 0xbf5c788c <----- (&b + 2) next array +----------+ ---►| ? | 0xbf5c7890 +----------+ ? means garbage value b[] array in memory from 0xbf5c787c to 0xbf5c7880 each cell is four bytes 

在上图中,存储单元的值是? 表示垃圾值而未分配( 0xbf5c7884内存未分配给我们的数组)。 值1存储在地址0xbf5c787c0xbf5c7880存储器中,该存储器在数组b[]分配。

让我们而不是打印值,我们使用(c + i)(&b + i)打印您在代码中访问的内存地址,为此请考虑以下程序:

 #include int main(){ int b[2] = {1, 2}; int i = 0; int *c = &b; //Give warning: "assignment from incompatible pointer type" printf("\n C address: "); // outputs correct values for (i = 0; i < 2; i++) { printf("%p ", (void*)(c + i)); } printf("\n B address: "); // outputs incorrect values/ and behaving differently for (i = 0; i < 2; i++) { printf("%p ", (void*)(&b + i)); // Undefined behavior } return 1; } 

输出:

  C address: 0xbf5c787c 0xbf5c7880 B address: 0xbf5c787c 0xbf5c7884 

检查此代码是否正常工作@ Codepade
注意, (c + i)打印值为1的单元格的正确地址,因此第一个代码中的输出是正确的。 而(&b + i)打印未分配给数组b[]地址值(在数组b[] )并且访问此内存会在运行时给出未定义的行为(不可预测)。

实际上b&b之间存在差异。

  • b是一个数组,它的类型是int[2]b在大多数表达式中作为第一个元素的地址衰减到int*中(读取: 一些exception,其中数组名称没有衰减成指向第一个元素的指针? )。 并且b + 1指向数组中的下一个int元素(注意图)。

  • &b是完整数组的地址,其类型为int(*)[2](&b + 1)指向未在程序中分配的int[2]类型的下一个数组(图中的通知,其中(&b + 1)分)。

要了解b&b之间的一些其他有趣的差异,请阅读: sizeof(&array)返回什么?

在第一个代码snipe中,当你执行c = &b ,你将数组的地址分配给int* (在我们的示例中为0xbf5c787c )。 使用GCC编译器,此语句将发出警告:“从不兼容的指针类型分配”。
因为c是指向int指针,所以*(c + i)打印存储在地址(c + i)整数。 对于i = 1 ,值(c + 1)指向数组中的第二个元素(在我们的示例中为0xbf5c7880 ),因此*(c + 1)正确打印2

关于赋值int *c = &b; 在第一个代码中我强烈建议阅读@ AndreyT的答案如下。 使用指针访问数组元素的正确和简单方法如下:

 int b[2] = {1, 2}; int *c = b; // removed &, `c` is pointer to int int i; for (i = 0; i < 2; i++){ printf("%d ", *(c + i)); // printf("%d ", c[i]); // is also correct statement } 

在第二个代码中,将i添加到&b使其指向外部分配的内存,而在printf语句中使用* dereference运算符访问内存会导致无效的内存访问,并且此代码在运行时的行为为Undefined 。 这就是第二段代码在不同执行时表现不同的原因。

您的代码编译,因为语法上它是正确的,但在运行时,操作系统内核可以检测到未分配的内存。 这可能导致操作系统内核向导致exception的进程发送信号核心转储(有趣的是:由于OS检测到进程的内存权限违规 - 对有效内存的无效访问给出:SIGSEGV,并且访问无效地址给出:SIGBUS)。 值得一提的是,您的程序可以在没有任何失败的情况下执行并产生垃

关于第二个代码,使用“指向数组的指针”打印数组的正确方法如下:

 #include int main(){ int b[2] = {1, 2}; int i; int (*c)[2] = &b; // `c` is pointer to int array of size 2 for(i = 0; i < 2; i++){ printf(" b[%d] = (*c)[%d] = %d\n", i, i, (*c)[i]); // notice (*c)[i] } return 1; } 

输出:

 b[0] = (*c)[0] = 1 b[1] = (*c)[1] = 2 

检查@ codepade 。 需要注意围绕*c括号,因为[]运算符的优先级高于* dereference运算符(而如果使用指向int的指针,则不需要括号,如上面的代码所示)。

这是因为指针算术运算ptr+i应用于的指针类型:

  • 在第一种情况下,将i添加到指向int的指针,这与索引数组相同。 由于指向数组的指针与指向其第一个元素的指针相同,因此代码可以正常工作。
  • 在第二种情况下,将i添加到指向两个int数组的指针。 因此,添加会使您超出分配的内存,从而导致未定义的行为。

以下是这一点的快速说明:

 int b[2] = {1,2}; printf("%p\n%p\n%p\n", (void*)&b, (void*)(&b+1), (void*)(&b+2)); 

在具有32位int的系统上,这将打印以8个字节分隔的地址 – int[2]的大小:

 0xbfbd2e58 0xbfbd2e60 0xbfbd2e68 
 int *c = &b; 

这实际上是无效的。 你需要一个演员。

 int *c = (int *) &b; 

这两个表达式:

  *(c+i) and *(&b+i) 

不一样。 在第一个表达式中, i被添加到int *而在第二个表达式中, i被添加到int (*)[2] 。 用int *c = (int *) &b; 你将int (*)[2]转换为int *c + i指向c + iiint元素但是&b+i指向&b+iint [2]元素,将指针值移动到实际数组对象之外。

第一个代码被破坏了。 分配

 int *c = &b; 

是无效的。 右侧的类型为int (*)[2] ,而左侧的对象的类型为int * 。 这些是不同的,不兼容的类型。 编译器应该通过发出诊断消息告诉您有关此错误的信息。 不要忽略编译器发出的诊断消息。

尽管遇到上述问题,代码由于非标准的编译器扩展而被编译器接受,这允许编译器将int (*)[2]指针转换为int *类型,保留指针的数值(物理地址)。 所以,最终你的int *指针指向int [2]数组的开头。 毫不奇怪,通过该指针访问内存可以让您查看数组的内容。

您的第二个代码也会以多种方式被破坏。 它不会受到第一个问题的影响,因为你不强制将&b值(同样具有类型int (*)[2] )的任何转换强制转换为其他任何东西,而是将指针算法直接应用于&b 。 根据指针算法的规则,表达式&b + 1产生一个指向超出原始b数组的指针。 取消引用这样的指针是非法的。 所以, *(&b + 1)已经产生了未定义的行为。 最重要的是,表达式*(&b + 1)具有类型int [2] ,它衰减到指针类型int * 。 因此,在第二个代码中,您尝试使用%d格式说明符打印int *值。 这也是未定义的行为。 这个未定义行为的表现forms就是你在第二个例子中看到的。

换句话说,在第一段代码中,你比第二段更幸运,这就是为什么前者的输出看起来更有意义。