C 代码中带有 union 的意外输出

Unexpected output in the C code with union

我不明白以下 C 代码的输出:

#include <stdio.h>
int main()
{
   union U
   {
      int i;
      char s[3];
   } u;
   u.i=0x3132;
   printf("%s", u.s);
   return 0;
}

初始内存为32位,是0x3132的二进制值,即

0000 0000 0000 0000 0011 0001 0011 0010.

如果0x3132的最后三个字节是s的值(不带前导零),则s[0]=0011,s[1]=0001,s[2]=0011.

这给出了 s=0011 0001 0011=787 的值。

问题:为什么输出是21而不是787

值0x3132在内存中表示为:0x32、0x31、0x0、0x0,因为字节顺序是小端。

printf 调用打印出联合成员s 表示的字符串。字符串是逐字节打印出来的。首先是 0x32,然后是 0x31,它们是字符的 ascii 值:'2''1'。然后打印停止,因为第三个元素是空字符:0x0。

请注意,int 的表示是实现定义的,可能不包含 4 个字节,并且可能有填充。因此,联合 s 的成员可能不代表字符串,在这种情况下,使用 %s 说明符调用 printf 将导致未定义的行为。

表示你的机器是little-endian,所以字节的存储顺序是相反的,像这样:

32 31 00 00

所以:s[0] = 0x32, s[1] = 0x31, s[2] = 0x00.

即使在理论上使用 "%s" 打印字符数组是未定义的行为,这仍然有效,它打印 0x32 (character '2')0x31 (character '1') 然后停止 0x00.

先看这段代码示例:

#include <inttypes.h> 
#include <stdio.h>
#include <stdint.h>
int main()
{
    union{
        int32_t i32;
        uint32_t u32;

        int16_t i16[2];
        uint16_t u16[2];

        int8_t i8[4];
        uint8_t u8[4];
    } u;

    u.u8[3] = 52;
    u.u8[2] = 51;
    u.u8[1] = 50;
    u.u8[0] = 49;

    printf(" %d %d %d %d \n", u.u8[3], u.u8[2], u.u8[1], u.u8[0]); // 52 51 50 49
    printf(" %x %x %x %x \n", u.u8[3], u.u8[2], u.u8[1], u.u8[0]); // 34 33 32 31
    printf(" 0x%x \n", u.i32); // 0x34333231

    return 0;
}

这里的并集只是以6种不同的方式访问u的内存。
您可以使用 u.i32 来读取或写入 int32_t
您可以使用 u.u32 读取或写入 uint32_t

您可以使用 u.i16[0]u.i16[1] 来读取或写入 int16_t 或 您可以使用 u.u16[0]u.u16[1] 来读取或写入 uint16_t

或者像这样写成uint8_t

u.u8[3] = 52;
u.u8[2] = 51;
u.u8[1] = 50;
u.u8[0] = 49;

并像这样阅读 int8_t:

printf(" %d %d %d %d \n", u.u8[3], u.u8[2], u.u8[1], u.u8[0]);

则输出为:

52 51 50 49

读作int32_t

printf(" 0x%x \n", u.i32);

则输出为:

0x34333231

因此,正如您在此示例代码中看到的那样,联合与许多 names/types 共享一个内存位置。

在你的示例代码中 u.i=0x3132; 这会在 u.i 内存中写入 0x3132,并且取决于你系统的字节顺序,这里是小端,然后你问 printf("%s", u.s); 来自编译器,所以 u.s 是类型 char 的数组,意思是指向 char 类型的常量指针,所以这个 printf("%s", u.s); 将读取 u.s[0] 并将其打印在输出 stdout 然后读取 u.s[1] 并将其打印在输出 stdout 上,依此类推...,直到其中一个 u.s[i] 为零。
这就是您的代码所做的,因此如果 u.s[0]、u.s[1]、u.s[2]、u.s[3] 的 none不为零,那么将读取联合之外的内存,直到找到一个零或发生系统内存故障 error

如果你这样写代码:

#include <stdio.h>

int main( void )
{
    union U
   {
      int i;
      char s[3];
   } u;

   u.i=0x3132;
   printf("%s", u.s);
   printf( "%8x\n", (unsigned)u.i);
}

然后你会看到 u.i 的内容是 0x0000000000003132,由于 Endianness

实际上存储为:0x3231000000000000

并且 0x00 不是可打印字符,因此第二次调用 printf() 的输出是 <blank><blank><blank><blank><blank><blank>3132 正如您所期望的那样

ascii 字符 1 是 0x31,ascii 字符 2 是 0x32,第一个 0x00 停止 %s 操作,所以第一个 printf() 输出 21 .