使用指针取消引用生成的模糊输出?

Ambiguous Output generated using Pointer dereferencing?

我遇到了以下程序,无法理解输出结果如何 -109 1683。输出结果如何?

 #include <stdio.h>

 int main()
{
 int k = 1683;
 char *a = (char *)&k;
 int *l  = &k;
 printf("%d " , *a);
 printf("%d" , *l);
  }
 Output is : -109 1683

如何取消引用指针 a 给我 -109

我希望它读取四字节整数的第一个字节。

1683二进制表示是00000000 00000000 00000110 10010011。所以读取第一个字节意味着输出应该是0 1683。幕后发生了什么,我听说了一些关于架构字节顺序的事情,但无法理解。

类型 'char' 和 'int' 在大多数平台上大小不同。具体来说 'int' 通常是 32 或 64 位(4 或 8 字节),而 char 只有 8 位(1 字节)。当 de-referencing a 'char' 时,您要求程序将 'int' 的 memory-location "interpret" 作为 'char'。

此外,'int' 的字节存储在 little-endian 或 big-endian 的内存中(Google)。因此,您的程序的结果将因平台而异。

如果您 运行 x86 上的代码(即 little-endian),您可能会看到 "correct" 值,如果您将 'int' 设置为值小于 128。

更新: 正如您正确指出的那样,您的 int 的 least-significant 字节是 10010011,十进制为 147,因此 大于 10000000 (128十进制)。由于设置了字节的 top-most 位,因此该值被解释为 -109.

的 2 的补码(Google 它)的负值

注意 1683 = 0x693.

如果我们假设:

  • 您的硬件架构是Little-Endian
  • 您的平台定义 CHAR_BIT 为 8

那么0x693中的第一个char就是0x93.

此时,注意:

  • unsigned中的2s补码格式:0x93 = 147
  • signed中的2s补码格式:0x93 = 147-256 = -109

它的发生是因为字节顺序:https://en.wikipedia.org/wiki/Endianness

你实际上取了字节10010011的地址,也就是-109

整数 1683 等于 0x00000693int 在现代系统上通常是 32 位)。在 little-endian 系统(如 x86 和 x86-64)上,它在内存中的布局类似于

+------+------+------+------+------+------+------+------+
| 0x93 | 0x06 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 |
+------+------+------+------+------+------+------+------+

当您初始化指针 a 时,您使其指向该序列中的第一个字节,即包含值 0x93 的字节,因此这就是您取消引用时获得的值 a.

那么现在的问题是值 0x93 是如何变成 -109 的。有两个原因:一个是编译器的 charsigned(如果 char 是有符号或无符号类型,它取决于编译器)。第二个原因是因为 two's complement arithmetic. Two's complement 是一种能够在像现代计算机这样的二进制机器上对有符号整数进行编码的方法。

基本上,对于单个 8 位字节,您可以通过取(无符号)十进制值减去 256 (28) 得到负值。 0x93的无符号十进制值为147147 - 256等于-109.

int 至少是 16 位,而 char 总是 1 个字节 (not necessary 8-bit)。数字 1683 的二进制形式是 00000110 10010011。由于您使用的是 char*,它将指向第一个字节。但那哪个先来呢? char 指的是哪一个? 00000110 还是 10010011?这取决于:

1) 如果你的系统使用little-endian字节序,则为后者,即10010011

00000110 10010011
         ^^^^^^^^

由于您使用的是 signed char 类型,因此 most-significant 字节将用作 'sign-bit',即如果它是 1,则字节代表负数。要获得 human-readable 值,即以 10 为基数的数字,您需要执行 two-complement。最后你会得到 -109.

2) 如果你的系统使用big-endian字节顺序,并且它使用16位int,它会是前者,即00000110。这很简单,它的 base-10 形式将是 6.

00000110 10010011
^^^^^^^^

如果使用 32 位 int,它将为零:

00000000 00000000 00000110 10010011
^^^^^^^^

如果你有类似的东西,

int k = 1683 ;
int *l = &k;

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

 int k = 1683;
 char *a = &k;
 int *l  = &k;

现在 pointer p 指向 y 但我们已将其声明为指向 char 的指针,因此它只会读取一个字节或 char 的任何字节。 1683 在二进制中将表示为

00000000 00000000 00000110 10010011

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

10010011 00000110 00000000 00000000

10010011address 00 Hypothetical address00000110address 01 等等。

BE:      00   01   02   03
       +----+----+----+----+   
    y: | 00 | 00 | 06 | 93 |
       +----+----+----+----+


LE:      00   01   02   03
       +----+----+----+----+
    y: | 93 | 06 | 00 | 00 |
       +----+----+----+----+

(In Hexadecimal)

所以现在如果你取消引用 pointer a 它将只读取第一个字节并且输出将是 -1 因为字节读取将是 10010011(因为我们指向 signed char ,所以 most-significant bit 是符号位。第一位 1 表示符号。10010011 = –128 + 16 + 2 + 1 = –109。)如果你取消引用 pointer l 它将完全读取 [=14= 的所有字节] 因为我们声明它是指向 int 的指针。输出将是 1234

而且如果你将指针 l 声明为 int *l 那么 *l 通常会读取 sizeof(int) 4 bytes(取决于机器架构)并且 *(l+1) 会还读了那么多字节。 char 或任何其他数据类型也是如此,指向它们的指针将读取大小为 的字节数,char1 byte