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

时间:2016-07-09 11:12:34

标签: c unions

我不理解以下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

4 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:2)

首先看到此代码示例:

#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

正如您在此示例中所看到的,代码联合共享一个具有许多名称/类型的内存位置。

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

答案 2 :(得分:1)

这意味着你的机器是 little-endian ,因此字节以相反的顺序存储,如下所示:

32 31 00 00

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

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

答案 3 :(得分:0)

如果您编写这样的代码:

<item name="android:scrollbarThumbVertical">@color/transparent</item>
if you don't have a transparent color defined in colors.xml, use the android library "transparent" with: <item name="android:scrollbarThumbVertical">@android:color/transparent</item>

然后你会看到u.i的内容是0x0000000000003132,由于Endianness,它实际上会被存储为:0x3231000000000000

和0x00不是可打印字符,因此第二次调用#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); } 的输出为printf()正如您所期望的那样

,ascii char <blank><blank><blank><blank><blank><blank>3132为0x31,ascii char 1为0x32,第一个0x00停止%s操作,因此第一个2输出printf()。< / p>