8

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

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));
}

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

4

4 回答 4

9

声明:

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

int创建一个包含两个值的数组1, 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,并且在数组中分配。 20xbf5c787c0xbf5c7880b[]

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

#include<stdio.h>
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)打印单元格的正确地址 value 12因此您的第一个代码中的输出是正确的。而(&b + i)打印未分配给数组的地址值b[](即在数组之外b[])并且访问此内存会在运行时产生未定义的行为(不可预测)。

实际上 和 之间是有区别b&b

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

  • &b是完整数组的地址,它的类型是int(*)[2](&b + 1)指向int[2]程序中未分配的下一个类型数组(请注意图中的(&b + 1)位置)。

要了解其他一些有趣的区别b&b阅读:返回什么sizeof(&array)

在第一个代码狙击中,当您这样做时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 语句中使用*取消引用运算符访问内存会导致无效的内存访问,并且此代码在运行时的行为是Undefined。这就是第二段代码在不同执行时表现不同的原因。

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

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

#include<stdio.h>
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是,需要括号括起来,因为运算符的优先级[]高于* 取消引用运算符(而如果您使用指向 int 的指针,则不需要像上面的代码那样使用括号)。

于 2013-07-15T20:16:26.603 回答
5

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

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

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

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

在具有 32 位ints 的系统上,这将打印由八个字节分隔的地址 - 大小为int[2]

0xbfbd2e58
0xbfbd2e60
0xbfbd2e68
于 2013-07-15T18:23:23.260 回答
1
int *c = &b;     

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

int *c = (int *) &b; 

两种表达方式:

 *(c+i)

 and 

 *(&b+i)

不一样。在第一个表达式i中添加到 aint *中,在第二个表达式i中添加到 aint (*)[2]中。与int *c = (int *) &b;您一起将 a 转换int (*)[2]int *. c + i指向的i第-个int元素c但是&b+i指向将指针值移动到实际数组对象之外的int [2]元素。b

于 2013-07-15T18:22:14.683 回答
1

第一个密码坏了。那作业

int *c = &b;

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

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

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

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

于 2013-07-16T16:06:02.573 回答