//foo.c
#include <stdio.h>
int main()
{
int a = 1;
int *pn = &a;
printf("%lldn", (long long int) pn);
char *pc = ((char *) pn ) + 4 * (sizeof(char));
printf("%lldn", (long long int) pn);
*pc = ' '; //This assignment changes the value of pn.
printf("%lldn", (long long int) pn);
}
在上述C程序中,字符指针pc
指向整数指针pn
旁边的位置。分配*pc = ' '
改变pn
的值。
gcc -o foo foo.c
./foo
这是输出:
140736942845732
140736942845732
140736942845696
你能理解为什么分配给*pc
会影响pn
的值吗?
在char *pc = ((char *) pn ) + 4 * (sizeof(char));
中,pc
被设置为指向pn
所指向位置之外的四个字节。pn
指向a
,它有四个字节长(在C实现中(。因此,我们通常不知道pc
现在指向的内存中有什么。编译器可以用它想要的任何方式组织内存:在内存中的a
(可能在堆栈上(之后,它可以放pn
或pc
或数据供自己使用,也可以什么都不放。
在*pc = ' ';
中,在pc
指向的位置放置一个空字符(值为零的字节(。当您使用一个指针写入内存时,该指针已被操作为位于它所取自的对象之外,该行为不是由C标准定义的。在这种情况下,编译器似乎在a
之后将pn
存储在内存中,并且以这种方式写入pc
将零字节写入pn
。
这里的编译器没有错误。当您在正确地址之外写入时,编译器没有义务提供任何特定行为。
另外,请注意,printf("%lldn", (long long int) pn);
不是打印指针的正确方式。要打印指针pn
,请使用printf("%pn", (void *) pn);
或包括<stdint.h>
和<inttypes.h>
并使用printf("%" PRIuPTR "n", (uintptr_t) pn);
。对于前一个代码,使用%p
,输出的形式由C实现选择。使用后一种代码,您可以对格式进行一些控制;您可以使用PRIuPTR
进行十进制输出,使用PRIxPTR
进行十六进制输出,还可以添加标志和其他控件(如printf("%#08" PRIxPTR "n", (uintptr_t) pn);
(来请求以"0x"开头的十六进制格式和足够的前导零,以便打印八位数字。
我觉得这是个bug。不过,公平地说,代码并不理想,因为我们试图访问无人认领的内存。请注意,如果我们首先分配内存,问题就会消失:
#include <stdio.h>
int main()
{
int a[2] = {1, 1};
int *pn = &(a[0]);
printf("%pn", pn);
char *pc = ((char *) pn ) + 4 * (sizeof(char));
printf("%pn", pn);
*pc = ' '; //This assignment changes the value of pn.
printf("%pn", pn);
}
我的系统输出(64位(:
$ gcc -o foo foo.c
$ ./foo
0x7ffffcfa6870
0x7ffffcfa6870
0x7ffffcfa6870
编辑:Eric很清楚pn
可能被分配在a
旁边的堆栈上;所以这有点道理。谜团解开(