此代码示例正确打印数组。
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
创建一个包含两个值的数组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
,并且在数组中分配。 2
0xbf5c787c
0xbf5c7880
b[]
我们不是打印值,而是打印您在代码中使用(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 1
,2
因此您的第一个代码中的输出是正确的。而(&b + i)
打印未分配给数组的地址值b[]
(即在数组之外b[]
)并且访问此内存会在运行时产生未定义的行为(不可预测)。
实际上 和 之间是有区别b
的&b
。
b
是一个数组,它的类型是int[2]
,在大多数表达式中b
衰减为第一个元素的地址(阅读:一些例外,其中数组名称没有衰减为指向第一个元素的指针?)。并指向数组中的下一个元素(注意图表)。int*
b + 1
int
&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 的指针,则不需要像上面的代码那样使用括号)。
这是因为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
s 的系统上,这将打印由八个字节分隔的地址 - 大小为int[2]
:
0xbfbd2e58
0xbfbd2e60
0xbfbd2e68
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
第一个密码坏了。那作业
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
格式说明符。这也是未定义的行为。这种未定义行为的表现就是您在第二个示例中看到的。
换句话说,在第一段代码中你比第二段更幸运,这就是为什么前者的输出看起来更有意义。