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

-3 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);
}
Run Code Online (Sandbox Code Playgroud)

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

-46 1234
Run Code Online (Sandbox Code Playgroud)

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

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

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

此程序不会导致此处指出的任何UB行为.

Sur*_*ain 14

如果你有类似的东西,

int x = 1234;
int *p = &x;
Run Code Online (Sandbox Code Playgroud)

如果您取消引用指针,p那么它将正确读取整数字节.因为你声明它是指针int.它将知道sizeof()运营商要读取多少字节.通常大小int4 bytes(对于32/64位平台),但它取决于机器,这就是为什么它将使用sizeof()运算符来知道正确的大小并将读取.

为了你的代码

 int y = 1234;
 char *p = &y;
 int *j  = &y;
Run Code Online (Sandbox Code Playgroud)

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

        00000000 00000000 00000100 11010010

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

        11010010 00000100 00000000 00000000

11010010address 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)
Run Code Online (Sandbox Code Playgroud)

所以,现在如果取消引用pointer p它只会读取第一个字节,结果应该是(-46在的情况下,signed char210在以下情况下unsigned char,根据C标准的char的签署岬是"实现定义.)作为读取的字节是11010010(因为我们指出signed char(在这种情况下是signed char).

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

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

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

  • @SurajJain这个答案大多是正确的.我会更正以下内容:*通常char为1字节*:它始终为1字节.*通常int的大小是4个字节*:如果是32/64位平台,则为真(例如,对于8位平台不是这样).同样@JohnBode在其答案中说`char*p =&y;`无效并且需要强制转换. (2认同)

Joh*_*ode 10

编写的代码有几个问题.

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

在线C 2011草案,§7.21.6.1,第9节:

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

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

printf( "%hhd ", *p );
Run Code Online (Sandbox Code Playgroud)

第二个问题是该行

char *p = &y;
Run Code Online (Sandbox Code Playgroud)

是违反约束 - char *并且int *不是兼容类型,并且可能具有不同的大小和/或表示2.因此,您必须显式地将源转换为目标类型:

char *p = (char *) &y;
Run Code Online (Sandbox Code Playgroud)

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

说了这么多,我拿了你的代码并添加了一个实用程序来转储程序中对象的地址和内容.下面介绍一下y,pj看起来像我的系统(SLES 10,GCC 4.1.2)上:

       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    ....
Run Code Online (Sandbox Code Playgroud)

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

BE:      A   A+1  A+2  A+3
       +----+----+----+----+
    y: | 00 | 00 | 04 | d2 |
       +----+----+----+----+
LE:     A+3  A+2  A+1   A
Run Code Online (Sandbox Code Playgroud)

在小端系统上,寻址字节是最不重要的字节,在这种情况下是0xd2(210无符号,有-46符号).

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

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


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

  • 这个答案目前说:_你通过尝试使用`%d`转换说明符_打印`char`对象的数字表示来调用未定义的行为 - 这是对它的礼貌,hogwash.关于它,绝对没有什么不确定的.有实现定义的行为,但这不是未定义的.它是实现定义的普通`char`是有符号还是无符号,这会影响`char`被转换为的`int`值,但这就是全部. (6认同)
  • 格式字符串到`printf()`之后的参数经历默认的促销规则([C11§6.5.2.2函数调用,6-7](http://port70.net/~nsz/c/c11/n1570. html#6.5.2.2p6))因为函数原型中的省略号. (4认同)