解引用这个指针给我-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,则行为是不确定的。
是的,当传递给可变参数函数时, char
types的对象被提升为int
; printf
是特殊的,如果你想要输出是明确的,那么参数和转换说明符的types必须匹配。 要用%u
, %o
或%x
打印带有%d
或unsigned 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)上y
, p
和j
样子:
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
在小端系统中,寻址字节是最不重要的字节,在这种情况下是0xd2
( 210
无符号, -46
符号)。
简而言之,您将打印该单字节的带符号的十进制表示forms。
至于更广泛的问题, expression式 *p
的types是char
, expression式 *j
的types是int
; 编译器只需要根据expression式的types。 编译器跟踪所有对象,expression式和types,因为它将源代码转换为机器代码。 所以当它看到expression式*j
,就知道它正在处理一个整数值并适当地生成机器码。 当它看到expression式*p
,它知道它正在处理一个char
值。
- 无可否认,我所知道的几乎所有的现代桌面系统对于所有的指针types都使用相同的表示,但对于更多古怪的embedded式或专用平台,这可能并非如此。
- 第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
11010010
在address 00
Hypothetical address
, 00000100
在address 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
时是-46
在signed 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
的存储器字节数,添加/减去正确的字节数,使其指向下一个对象,等等