取消引用这个指针给了我-46,但我不知道为什么

问题描述 投票:-5回答:4

这是我运行的程序:

#include <stdio.h>

int main(void)
{
    int y = 1234;
    char *p = &y;
    int *j = &y;
    printf("%d %d\n", *p, *j);
}

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

-46 1234

我把这个程序写成了一个实验,不知道它会输出什么。我期待可能来自y的一个字节。

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

正如其他人指出的那样,我必须进行明确的施法才能导致UB。我没有改变从char *p = &y;char *p = (char *)&y;的那条线,所以我没有使下面的答案无效。

该程序没有引起任何UB行为,如指向here

c pointers casting dereference
4个回答
13
投票

如果你有类似的东西,

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

如果您取消引用指针p然后它将正确读取整数字节。因为你声明它是指向int的指针。它将知道sizeof()运算符要读取多少字节。通常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,它将只读取第一个字节,输出将是-46signed char210unsigned char,根据C标准,普通字符的签名是“实现定义。”作为字节读取将是11010010(因为我们指出signed char(在这种情况下它是signed char)。

在您的PC上,负数表示为2's Complement,因此most-significant bit是符号位。第一位1表示标志。 11010010 = –128 + 64 + 16 + 2 = –46,如果你取消引用pointer j,它将完全读取int的所有字节,因为我们宣称它是指向int的指针,输出将是1234

如果你将指针j声明为int *j,那么*j将在这里读取sizeof(int) 4个字节(取决于机器)。与char或指针指向它们的任何其他数据类型相同的是,读取的字节大小为多,char为1个字节。

正如其他人指出的那样,你需要明确地转换为char*,因为char *p = &y;是一个约束违规 - char *int *不是兼容的类型,而是写char *p = (char *)&y


10
投票

编写的代码有几个问题。

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

Online C 2011 draft,§7.21.6.1,第9款:

If a conversion specification is invalid, the behavior is undefined.282) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

是的,当传递给可变函数时,char类型的对象被提升为int; printf是特殊的,如果你想要明确定义输出,那么参数的类型和转换说明符必须匹配。要使用char%dunsigned char%u打印%o%x参数的hh的数值,必须使用printf( "%hhd ", *p ); 长度修改器作为转换规范的一部分:

char *p = &y;

第二个问题是该行

char *

是违反约束 - int *char *p = (char *) &y; 不兼容类型,可能有不同的大小和/或表示2。因此,您必须显式地将源转换为目标类型:

void *

当其中一个操作数是y时,会发生此规则的一个例外;那么演员阵容是没有必要的。

说了这么多,我拿了你的代码并添加了一个实用程序来转储程序中对象的地址和内容。这是pj 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 .... 在我的系统上的样子(SLES-10,gcc 4.1.2):

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

我在x86系统上,它是little-endian,因此它存储多字节对象,从最低地址的最低有效字节开始:

0xd2

在小端系统上,被寻址的字节是最不重要的字节,在这种情况下是210-46 unsigned,*p signed)。

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

至于更广泛的问题,表达式char的类型是*j,表达式int的类型是*j;编译器只是按表达式的类型。编译器在将源转换为机器代码时会跟踪所有对象,表达式和类型。因此,当它看到表达式*p时,它知道它正在处理整数值并适当地生成机器代码。当它看到表达式char时,它知道它正在处理sizeof值。


  1. 不可否认,我所知道的几乎所有现代桌面系统都对所有指针类型使用相同的表示,但对于更多奇怪的嵌入式或专用平台,可能并非如此。
  2. §6.2.5,第28条。


2
投票

(请注意,这个问题的答案是指问题的原始形式,它询问程序如何知道要读取多少字节,等等。我在此基础上保留它,尽管地毯已被拉出来。)

指针指的是内存中包含特定对象的位置,必须以特定的步幅大小递增/递减/索引,反映std::cout << ptr指向的类型。

指针本身的可观察值(例如通过++ptr)不需要反映任何可识别的物理地址,sizeof(*ptr)也不需要将所述值增加1,sizeof或其他任何东西。指针只是对象的句柄,具有实现定义的位表示。该表示对用户而言并不重要。用户应该使用指针的唯一方法是......好吧,指向东西。谈论它的地址是不可移植的,只在调试时有用。

无论如何,简单地说,编译器知道读取/写入多少字节,因为指针是键入的,并且该类型具有已定义的ptr,表示和映射到物理地址。因此,基于该类型,ptr上的操作将被编译为适当的指令,以便计算实际硬件地址(再次,不需要对应于sizeof的可观察值),读取正确的chat *p = (char*)&y; 内存'字节数',加/减正确的字节数,使其指向下一个对象等。


1
投票

首先阅读警告:从不兼容的指针类型初始化[默认启用] char * p =&y;

这意味着您应该根据标准§7.21.6.1,子条款9(由@john Bode指出)进行显式类型转换以避免未定义的行为

int y =1234;

y

这里local variablestack,它将存储在RAMlittle endian部分。在Linux机器整数根据4 bytes格式存储在内存中。假设为y保留的内存的0x100是从0x104 ------------------------------------------------- | 0000 0000 | 0000 0000 | 0000 0100 | 1101 0010 | ------------------------------------------------- 0x104 0x103 0x102 0x101 0x100 y p j

j

如上所述,p0x100都指向相同的地址*p,但当编译器将执行p,因为signed character pointer默认是sign bit它将检查sign bit和这里1sign bit意味着有一件事是确定它将要打印的输出是负数。

如果1 actual => 1101 0010 (1st byte) ones compliment => 0010 1101 +1 ------------ 0010 1110 => 46 and since sign bit was one it will print -46 ,即负数,负数存储在Memory中作为2的赞美So

%u

如果您正在使用unsigned格式说明符打印not等效的打印,它将sign bi检查1 bytet,最后printf("%d\n",*j); 中的任何数据被打印。

最后

j

在上面的声明,同时取消引用signed pointer默认是int和它的0指针所以它将检查第31位的符号,这是positive意味着输出将是qazxswpoi否,这是1234。

© www.soinside.com 2019 - 2024. All rights reserved.