C语言陷阱与技巧第34节,联合体union的性质和误区,union成员必须等长度吗?使用union必须考虑大小端吗?

上一节讨论了C语言的 union 语法的一个使用场景,读者应该发现,union 成员共享一块内存的特性使得C语言程序员能够写出更加节约资源的程序。不过也有读者在看了上一节文章后,评论或者私信说了一些关于C语言 union 的特性,这些回复有些是不严谨的。

如果不能完全掌握 union 语法,在使用时难免会觉得困惑,甚至跳入“陷阱”,写出有隐患的C语言代码。我稍稍总结了一下读者回复中关于 union 语法的误解,主要可以分为两大类。

误区1:union 成员必须等长度

下面这段C语言代码节选自上一节:

struct video_info{
     int alg;
     time_t time;
};
struct audio_info{
     int sample_rate;
    int chnnl_cnt;
};
union av{
    struct video_info video;
    struct audio_info audio;
};

有读者评论称:“要是union不同成员的大小不一样就不能这么用了”。他认为C语言的 union 成员必须是等长的,否则就不可使用。这么认为也有一定的理由:union 成员共用一块内存,如果成员不等长,那差异长度怎么算呢?

那么,C语言中的 union 成员到底是不是必须长度相等呢?下面是一个例子,请看:

union u{
    char c;
    int i;
};

显然,成员 c 和 i 长度并不相等,那是否这么定义 u 就不对了呢?我们编写相应的C语言代码测试之:

int main()
{
    union u test;

    test.c = 1;
    printf("sizeof union test = %ld\n", sizeof(test));

    return 0;
}

编译这段代码并执行,得到如下输出:

# gcc t.c
# ./a.out 
sizeof union test = 4

可见编译器并没有报错。虽然我们仅使用了 union 的成员 c(占用1字节),但是 test 仍然占用了 4 个字节。显然,即使 union 的成员不等长,使用起来也完全没有问题,而且 union 占用内存的空间等于最长的那个成员占用的内存空间。

误区2:使用 union 必须考虑大小端问题

有读者(@Fishmoo)回复说:“共用体(union)要在确定硬件大小端的情况下使用”。这个观点对不对呢?在回答这个问题之前,首先需要了解“大小端”的概念。

简单来说,“大小端”是指数据在内存中的字节顺序。例如 int 型变量 a=0x12345678 占用 4 个字节,在大端字节序的机器上,int 数据的高位位于低地址。在小端字节序的机器上,int 数据的高位位于高地址。

关于“大小端”这个名称,有一段比较有趣的故事:

Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因:大家都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可以那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受。

那么,大小端和 union 有什么关系呢?读者应该明白,C语言中 union 语法最重要的性质就是成员共享一块内存区域,我们改写上面的C语言代码:

union u{
    char c[4];
    int i;
};

因为在我的机器上,成员 i 占用 4 个字节内存空间,所以将成员 c 改为数组 char c[4]。现在编写下面这段C语言代码:

union u test;
test.i = 0x12345678;
for(j=0; j<sizeof(test.c); j++)
    printf("0x%x\n", test.c[j]);

这段C语言代码会输出什么呢?因为成员 c 和 成员 i 共享同一块内存,所以 c 在内存中的起始地址和 i 的一致,i 是一个 4 字节变量,它的值在内存中分布因机器“大端”和“小端”而异。编译这段C语言代码并执行,得到如下输出:

# gcc t.c
# ./a.out 
0x78
0x56
0x34
0x12

可见,高位在低地址,此时C语言程序运行在“大端”机器上。读者可自行测试上面这段C语言代码,应该会发现,如果自己的机器是“小端”架构,则C语言程序最终的输出会与这里有一定的差异。

现在知道“大小端”与C语言 union 的关系了。那是不是只要使用 union,就一定要考虑“大小端”问题呢?当然不是了,例如上一节中的 union 例子,就完全不需要考虑“大小端”问题。只有涉及到使用“小变量”解释“大变量”内存区域时,才需要考虑“大小端”问题。

看了本专栏之前文章的读者应该明白,C语言中的数据类型的一大作用就是告诉编译器该如何解释它所在内存里存放的数据的。例如一个 4 字节内存区域:

编译器要将这块内存里的数据取出使用,先要知道该怎么取——它这块内存可以表示 4 个 char 型变量,也可以表示 2 个 short 型变量,还可以表示 1 个 int 型变量。如果告诉编译器这段内存表示一个 int 型数据,编译器会自动按照当前机器的“大小端”解释这个数据,就无需程序员再多操心了。如果告诉编译这段内存表示 4 个 char 型变量(小变量),则编译器会按照地址顺序解释这段内存,输出就与“大小端”有关了。

小结

本节简要介绍了C语言 union 语法的性质,较为详细的讨论了上一节读者关于 union 的两个误区。现在应该明白,C语言的 union 成员不一定是等长的,使用 union 并不总是需要考虑“大小端”问题。事实上,“大小端”问题不是 union 语法的专属考虑,在C语言程序开发中,很多情况都需要考虑这个问题,以后有机会再说了。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif