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

时间:2013-07-15 18:19:22

标签: c pointers

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

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 个答案:

答案 0 :(得分:9)

宣言:

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

创建一个包含值int1的两个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的存储器未分配给我们的数组)。值12存储在地址0xbf5c787c0xbf5c7880的内存中,该值在数组b[]中分配。

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

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

  • b是一个数组,其类型为int[2]b衰减为int*,作为大多数表达式中第一个元素的地址(读取:{{3} }})。并且b + 1指向数组中的下一个int元素(请注意图表)。

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

要了解b&b之间的其他一些有趣的差异,请阅读:some exceptions where array name not decaying into a pointer to first element?

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

关于第一段代码中的作业int *c = &b;,我强烈建议您阅读下面的@ AndreyT What does sizeof(&array) return?。使用指针访问数组元素的正确和简单方法如下:

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语句中使用*取消引用运算符访问内存会导致无效的内存访问和此行为运行时的代码是answer。这就是第二段代码在不同执行时表现不同的原因。

您的代码编译因为语法正确,但在运行时,OS内核可以检测到未分配内存的访问。这可能导致操作系统内核向导致异常的进程发送信号核心转储(有趣的是:由于OS检测到进程的内存权限违规 - 对有效内存的无效访问给出: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  

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

答案 1 :(得分:5)

这是因为指针算术运算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

答案 2 :(得分:1)

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指向i的{​​{1}} - int元素,但c指向&b+i的{​​{1}}元素移动指针值在实际数组对象之外。

答案 3 :(得分:1)

第一个代码被破坏了。作业

int *c = &b;

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

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

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

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