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

时间:2016-01-16 10:46:08

标签: c pointers casting dereference

这是我运行的程序:

#include <stdio.h>

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

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

-46 1234

我把这个程序写成了一个实验,并不知道它将要输出什么。我期待y可能只有一个字节。

正在发生的事情&#34;幕后花絮&#34;这里?解除引用p如何为我提供-46

正如其他人所指出的那样,我必须进行明确的施法才能导致UB。我没有将该行从char *p = &y;更改为char *p = (char *)&y;,因此我不会使下面的答案无效。

此程序不会引起任何UB行为,如指向here

4 个答案:

答案 0 :(得分: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

11010010位于address 00 Hypothetical address00000100位于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,它将仅读取第一个字节,如果-46,则signed char210 unsigned char根据C标准,普通字符的签名是#34;实现定义。)字节读取将是11010010(因为我们指出signed char(在这种情况下它是{{} 1}})。

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

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

正如其他人指出的那样,您需要明确转换为char,因为char*违反了约束 - char *p = &y;char *不是兼容类型,而是写{{ 1}}。

答案 1 :(得分:10)

编写的代码存在一些问题。

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

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

如果转换规范无效,则行为未定义.282)如果有任何参数 不是相应转换规范的正确类型,行为是 未定义。

是的,char类型的对象在传递给可变函数时会被提升为int; printf是特殊的,如果您希望输出定义良好,那么参数的类型和转换说明符必须匹配。要使用char%dunsigned char打印带有%u%o参数的%x的数值,您必须使用{ {1}}长度修饰符作为转化规范的一部分:

hh

第二个问题是该行

printf( "%hhd ", *p );

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

int *

当其中一个操作数为char *p = (char *) &y; 时,会发生此规则的一个异常;那么演员阵容就没有必要了。

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

j

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

       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    ....

在little-endian系统上,被寻址的字节是最不重要的字节,在这种情况下是BE: A A+1 A+2 A+3 +----+----+----+----+ y: | 00 | 00 | 04 | d2 | +----+----+----+----+ LE: A+3 A+2 A+1 A 0xd2无符号,210签名)。

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

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

<小时/>

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

答案 2 :(得分:2)

(请注意这个答案是指问题的原始形式,它询问程序如何知道要读取多少字节,等等。尽管有地毯,但仍然保留了这个字节。被从它下面拉出来。)

指针指向内存中包含特定对象的位置,并且必须以特定的步幅大小递增/递减/索引,反映sizeof指向的类型。

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

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

答案 3 :(得分:1)

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

这意味着您应该根据标准§7.21.6.1,子条款9 (由@john Bode指出)

来执行显式类型转换以避免未定义的行为
chat *p = (char*)&y;

int y =1234;

此处ylocal variable,它将存储在stack的{​​{1}}部分。在Linux中,机器整数根据{{1}存储在内存中格式。假设为RAM保留的little endian内存来自4 bytesy

0x100

如上所述,0x104 ------------------------------------------------- | 0000 0000 | 0000 0000 | 0000 0100 | 1101 0010 | ------------------------------------------------- 0x104 0x103 0x102 0x101 0x100 y p j 都指向同一地址j但编译器将执行p,因为0x100*p默认情况下,它会检查p,此处signed character pointersign bit表示有一件事是确定要打印的输出是负数

如果sign bit1,即负数且负数存储在内存中为2的赞美那么

sign 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 等效项,则会%u检查unsigned t,最后not中的数据是sign bi 1}}被打印出来。

终于

1 byte

在上面的语句中,在解除引用printf("%d\n",*j); 时默认为j及其signed pointer指针,因此它会检查第31位 符号int表示输出为0否,即1234。