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
.
我不明白以下 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
.