取消引用这个指针给我 -46,但我不确定为什么

Dereferencing this pointer gives me -46, but I am not sure why

这是我的程序 运行:

#include <stdio.h>

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

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

-46 1234

我写这个程序是为了实验,不确定它会输出什么。我期待 y.

可能有一个字节

这里发生了什么"behind-the-scenes"?取消引用 p 如何给我 -46

正如其他人所指出的,我必须进行显式转换才能不导致 UB。我不会将该行从 char *p = &y; 更改为 char *p = (char *)&y;,这样我就不会使下面的答案无效。

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

如果你有类似的东西,

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 的指针,因此它只会读取一个字节或 char 的任何字节。 1234 在二进制中将表示为

00000000 00000000 00000100 11010010

现在,如果您的机器是小端字节序,它将存储颠倒它们的字节

11010010 00000100 00000000 00000000

11010010address 00Hypothetical 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 它将只读取第一个字节并且输出将是(-46如果是 signed char210 如果是 unsigned char,根据 C 标准,普通 char 的 signed-ness 是“实现定义的。”因为字节读取将是 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.

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

指针指的是内存中包含特定对象的位置,并且必须 incremented/decremented/indexed 具有特定的步长,反映 sizeof 指向的类型。

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

无论如何,简单地说,编译器知道 read/write 有多少字节,因为指针是类型化的,并且该类型具有定义的 sizeof、表示和到物理地址的映射。因此,基于该类型,对 ptr 的操作将被编译为适当的指令,以计算真实的硬件地址(同样,不需要对应于 ptr 的可观察值),请阅读正确的sizeof 内存数量 'bytes',add/subtract 正确的字节数,因此它指向下一个对象,等等

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

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

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 类型的对象在传递给可变参数函数时被提升为 intprintf 是特殊的,如果您希望输出定义明确,那么参数的类型和转换说明符 必须 匹配。要使用 %dunsigned char 参数以及 %u%o%x 打印 char 的数值,您必须使用 hh 作为转换规范一部分的长度修饰符:

printf( "%hhd ", *p );

第二个问题是

char *p = &y;

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

char *p = (char *) &y;

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

话虽如此,我采用了您的代码并添加了一个实用程序,用于转储程序中对象的地址和内容。这是 ypj 在我的系统(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    ....

我使用的是小端 x86 系统,因此它存储多字节对象,从最低地址的最低有效字节开始:

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

在小端系统上,寻址字节是最低有效字节,在本例中为 0xd2210 无符号,-46 有符号)。

简而言之,您正在打印该单个字节的带符号的十进制表示形式。

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


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

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

这意味着你应该显式类型转换以避免根据标准§7.21.6.1,子条款9的未定义行为(由@指出约翰博德)作为

chat *p = (char*)&y;

int y =1234;

此处ylocal variable,它将存储在RAMstack部分。在Linux中,机器整数存储在内存中根据 little endian 格式。假设为 y 保留的内存 4 bytes0x1000x104

    -------------------------------------------------
    | 0000 0000 | 0000 0000 | 0000 0100 | 1101 0010 |
    -------------------------------------------------
    0x104      0x103       0x102       0x101       0x100
                                                    y
                                                    p
                                                    j

如上所述,jp 都指向相同的地址 0x100 但是当编译器将执行 *p 因为 psigned character pointer 默认情况下它会检查 sign bit 并且这里 sign bit1 意味着一件事是确定它要打印的输出是 负数

如果sign bit1即负数,负数作为2的补码存储在内存中所以

    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 等价物,它将 not 检查 sign bit,最后是其中的任何数据1 byte 得到打印。

终于

printf("%d\n",*j);

在上面的语句中,在取消引用 j 时默认为 signed pointer 并且它是一个 int 指针,因此它将检查 第 31 位 对于 sign,即 0 意味着输出将是 positive 否,即 1234.