直接使用指向数组和数组地址的指针不一致
此代码示例正确打印数组。
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
存储在地址0xbf5c787c
和0xbf5c7880
存储器中,该存储器在数组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 + i
第i
个int
元素但是&b+i
指向&b+i
的int [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就是你在第二个例子中看到的。
换句话说,在第一段代码中,你比第二段更幸运,这就是为什么前者的输出看起来更有意义。