解引用这个指针给我-46,但是我不知道为什么

这是我跑的一个程序:

#include <stdio.h> int main() { int y = 1234; char *p = &y; int *j = &y; printf("%d " , *p); printf("%d" , *j); } 

我对输出有些困惑。 我所看到的是:

 -46 1234 

我作为一个实验写了这个程序,并不知道它会输出什么。 我期待y可能有一个字节。

这里发生了什么“幕后”? 如何解引用p给我-46?

更新
由于指出了其他我必须做明确铸造不会导致UB.I不改变这一行char *p = &y to char *p = (char *)&y以便我不在下面的答案无效。

代码中存在一些问题。

首先,通过尝试使用%d转换说明符来打印char对象的数字表示forms来调用未定义的行为

Online C 2011草案 ,§7.21.6.1,子条款9:

如果转换规范无效,则行为是不确定的。 如果任何参数不是相应转换规范的正确types,则行为是不确定的。

是的,当传递给可变参数函数时, chartypes的对象被提升为int ; printf是特殊的,如果你想要输出是明确的,那么参数和转换说明符的types必须匹配。 要用%u%o%x打印带有%dunsigned char参数的char的数字值,必须使用hh长度修饰符作为转换规范的一部分:

 printf( "%hhd ", *p ); 

第二个问题就是这一行

 char *p = &y; 

是违反约束的 – char *int *是不兼容的types,并且可能具有不同的大小和/或表示2 。 因此,您必须明确地将源代码转换为目标types:

 char *p = (char *) &y; 

这个规则的一个例外是当其中一个操作数是void * ; 那么演员是没有必要的。

说了这么多,我把你的代码,并添加一个实用程序,转储程序中的对象的地址和内容。 下面是我的系统(SLES-10,gcc 4.1.2)上ypj样子:

  Item Address 00 01 02 03 ---- ------- -- -- -- -- y 0x7fff1a7e99cc d2 04 00 00 .... p 0x7fff1a7e99c0 cc 99 7e 1a ..~. 0x7fff1a7e99c4 ff 7f 00 00 .... j 0x7fff1a7e99b8 cc 99 7e 1a ..~. 0x7fff1a7e99bc ff 7f 00 00 .... 

我在x86系统上,这是一个小端,所以它存储的多字节对象的最低地址的最低有效字节开始:

 BE: A A+1 A+2 A+3 +----+----+----+----+ y: | 00 | 00 | 04 | d2 | +----+----+----+----+ LE: A+3 A+2 A+1 A 

在小端系统中,寻址字节是最不重要的字节,在这种情况下是0xd2210无符号, -46符号)。

简而言之,您将打印该单字节的带符号的十进制表示forms。

至于更广泛的问题, expression式 *p的types是charexpression式 *j的types是int ; 编译器只需要根据expression式的types。 编译器跟踪所有对象,expression式和types,因为它将源代码转换为机器代码。 所以当它看到expression式*j ,就知道它正在处理一个整数值并适当地生成机器码。 当它看到expression式*p ,它知道它正在处理一个char值。


  1. 无可否认,我所知道的几乎所有的现代桌面系统对于所有的指针types都使用相同的表示,但对于更多古怪的embedded式或专用平台,这可能并非如此。
  2. 第6.2.5节,子条款28。

如果你有类似的东西,

 int x = 1234 ; int *p = &x; 

如果您解引用指针p那么它将正确读取整数字节。 因为你声明它是指向int指针。 它将知道sizeof()运算符要读取多less个字节。 通常int大小是4 bytes (对于32/64位平台),但它是依赖于机器的,这就是为什么它将使用sizeof()运算符来知道正确的大小,并将读取。现在为您的代码

  int y = 1234; char *p = &y; int *j = &y; 

现在pointer p指向y但我们已经声明它是指向一个char指针,所以它只会读取一个字节或任何字节字符。 1234在二进制将被表示为

00000000 00000000 00000100 11010010

现在,如果你的机器是小端,它将存储反转它们的字节

11010010 00000100 00000000 00000000

11010010address 00 Hypothetical address00000100address 01等等。

 BE: 00 01 02 03 +----+----+----+----+ y: | 00 | 00 | 04 | d2 | +----+----+----+----+ LE: 00 01 02 03 +----+----+----+----+ y: | d2 | 04 | 00 | 00 | +----+----+----+----+ (In Hexadecimal) 

因此,现在如果解引用pointer p ,它将只读取第一个字节,输出将是(在signed char时是-46signed char 210 ,根据C标准,普通字符的unsigned char是“实现定义的”字节读取将是11010010 (因为我们指出了有signed char (在这种情况下,它是有signed char )。

在你的电脑负数表示为2的补码,所以most-significant bit是符号位。 第一位1表示符号。 11010010 = –128 + 64 + 16 + 2 = –46 ,如果你取消引用pointer j ,它将完全读取int所有字节,因为我们声明它是指向int指针,输出将是1234

而且,如果您将指针j声明为int *j那么*j将在这里读取4个字节(与机器相关)的sizeof(int) )。 char和其他任何数据types一样,指向它们的指针将读取大小为字节的字节, char为1个字节。

当其他人指出,你需要显式强制char*作为char *p = &y; 是违反约束 – char *int *不是兼容types,而是写入char *p = (char *)&y

(请注意,这个答案指的是问题的原始forms,它询问程序如何知道要读取多less字节等等。尽pipe地毯已经从地下拔出,但我依然保持这种状态。)

指针是指内存中包含特定对象的位置,必须以特定的步长大小递增/递减/索引,反映指向types的sizeof

指针本身的可观察值(例如,通过std::cout << ptr )不需要反映任何可识别的物理地址, ++ptr也不需要将所述值递增1, sizeof(*ptr)或其他任何值。 指针只是一个对象的句柄,具有实现定义的位表示。 这种表示方式对用户来说并不重要。 用户应该使用指针的唯一的事情就是…指向东西。 谈论它的地址是不可移植的,只在debugging时才有用。

无论如何,编译器知道要读/写多less字节,因为指针是键入的,而且该types具有已定义的sizeof ,表示forms以及映射到物理地址的大小。 因此,基于这种types,对ptr操作将被编译为适当的指令,以便计算真实的硬件地址(同样不需要对应于ptr的可观察值),读取正确sizeof的存储器字节数,添加/减去正确的字节数,使其指向下一个对象,等等