C语言学习篇(23)——共用体union

引言

在C语言中,每个数据类型都有其特定的作用或使用场景。 前面我们详细的分析了数组,结构体,今天我们来讲讲共用体(union),所谓的共用体就是使用覆盖技术让数据成员之间相互覆盖,以实现不同数据成员共用同一段内存。先引出概念,接下来我们从共用体与其他的数据类型区别,到共用体类型如何定义,哪些场景会使用共用体等等,逐步分析。

共用体和结构体区别

共用体(union)和结构体(struct)在类型定义, 变量定义,使用方法上都非常相似:

<code>//共用体类型定义
union test
{
\tint a
\tchar b;
};/<code>

变量的定义和使用:

<code>//定义共用体变量t1
\tunion test t1;
\t
//使用共同体t1成员
\tt1.a = 23;
\t/<code>

那两者有什么区别呢?结构体好比是一个袋子,袋子里面装了各种各样的东西,每个东西都是独立存在的,占据了袋子体积; 而共用体的各个成员是一体的,彼此不独立,它们使用同一块内存空间,可以理解为:有时候是这个元素,有时候是另外一个元素(就像是一个多面人),有不同的形态,更准确的说是同一块内存空间有多种解释方式!通过以下示意图感受下:

C语言学习篇(23)——共用体union

接下来我们通过实例代码,来理解其本质区别:

<code>#include <stdio.h>

//定义结构体类型
struct test1
{
\tint a;
\tchar b;
};

//定义共用体类型
union test2
{
\tint a;
\tchar b;
};

int main(void)
{
//定义结构体变量t1
\tstruct test1 t1;
//定义共用体变量2
\tunion test2 t2;
\t
\tt1.a = 23;
\tt2.a = 24;
\t
//打印变量元素
\tprintf("t1.b = %d.\\n", t1.b);
\tprintf("t2.b = %d.\\n", t2.b);

//打印结构体t1元素的地址
printf("&t1.a = %p.\\n", &t1.a);
\tprintf("&t1.b = %p.\\n", &t1.b);
\t
//打印共用体t2元素地址
\tprintf("&t2.a = %p.\\n", &t2.a);
\tprintf("&t2.b = %p.\\n", &t2.b);
\t
\treturn 0;

}/<stdio.h>/<code>

说明:

1.我们分别定义了结构体变量t1, 和共用体变量t2

2. 初始化了结构体t1中的元素a, 赋值为23,但是元素b未初始化

3. 初始化了共用体t2中的元素a, 赋值为24,同样元素b未初始化

4. 之后我们分别打印了t1和t2成员的地址


接下来我们编译运行,看下结果:

C语言学习篇(23)——共用体union

可以看到结构体t1的元素b未初始化,是一个随机值(因为我们定义的t1是局部变量,定义在栈上,如果不清楚什么是栈,什么是堆,以及它们的特性,可以查阅我之前的文章~)。

而共用体t2元素b未初始化, 但是它的值与元素a居然相同(这不是凑巧哦,不相信的小伙伴可以自己测试下,当然前提是a的初始化的值不能超过元素b的类型值域, 同时这里会涉及大小端的问题,这个之后会详细分析)。同时可以看到结构体t1的元素a和b的地址是不同的, 而共用体t2的元素a和b地址是完全一致!相信到这里大家能基本了解了共用体的特性: 成员之间共用一块内存空间,只是解析方式不同。

共用体的大小

经过上面的讲述,大家知道了共用体的成员其实是共享了一块内存空间,哪么当我们顶一个共用体类型时,它实际占用的内存空间是多少呢?

我们先举一个简单的例子:

<code>#include <stdio.h>

union test
{

\tint a;
\tchar b;\t
};

int main(void)
{
\tunion test t1;
\t
//打印共用体成员的数据类型大小
\tprintf("sizeof(int) = %d.\\n", sizeof(int));
\tprintf("sizeof(char) = %d.\\n", sizeof(char));
\t
//打印共用体大小
\tprintf("sizeof(union test) = %d.\\n", sizeof(union test));
\t
\treturn 0;
}/<stdio.h>/<code>

编译运行结果:

C语言学习篇(23)——共用体union

可以看到共用体变量t1的内存大小为4字节, 即原则上,共用体大小取决于占据最多内存的成员的长度。需要注意这里用的“原则”一词,因为有时候需要考虑内存对齐问题。 我们通过以下例子来加深理解:

<code>#include <stdio.h>

//通过__attribute__指令设置1字节对齐
__attribute__((aligned(1)))
struct mystruct
{
\tchar a;
\tint b;
\tint c;
\tint d;
\tint e;
\tdouble f;
\tchar g;
\tshort h;
\tdouble i;
\tfloat j;
\t
}__attribute__((packed));

//通过__attribute__指令设置1字节对齐
__attribute__((aligned(1)))
union test
{
\tint a;
\tchar b;
\tfloat c;
\tdouble d;
\t
\tstruct mystruct my1;
}__attribute__((packed));

int main(void)
{//定义了共用体变量t1
\tunion test t1;

\t
//打印了结构体大小
\tprintf("sizeof(struct mystruct) = %d.\\n", sizeof(struct mystruct));
//打印共用体大小
\tprintf("sizeof(union test) = %d.\\n", sizeof(union test));
\t
\treturn 0;
}/<stdio.h>/<code>

分析:

1. 首先我们定义了结构体类型mystruct, 并使用__attribute__指令设置为1字节对齐

2. 定义了共用体类型,其中一个成员是上面定义的结构体mystruct类型, 同时也使用__attribute__指令设置为1字节对齐

3. 定义了共用体变量t1

4. 分别打印了结构体mystruct类型大小,和共用体test 类型大小

OK, 编译运行:

C语言学习篇(23)——共用体union

我们可以看到共用体的大小就等于成员my1(结构体类型)的大小,再次验证了上面的结论。


共用体的访问方式

上面我们举得的例子都是使用下标式访问共用体成员,还是和数组,结构体一样, 下标式访问知识编译器提供的一种语法糖, 方便开发人员使用和理解, 但是我们需要了解其本质: 共享一块内存,只是解析方式不同。 因此我们可以通过指针方式来访问,这也是真正理解共用体的判断标准。

<code>#include <stdio.h>

union test
{
\tint a;
\tfloat b;
};

int main(void)
{
\tunion test t1;
\t
//下标式访问
\tt1.a = 1123477094;
\tprintf("t1.b = %f.\\n", t1.b);
\t
//指针式访问
\tint a = 1123477094;
\tprintf("指针方式: t1.b = %f.\\n", *(float *)&a);
\t
\treturn 0;

}/<stdio.h>/<code>

编译运行:

C语言学习篇(23)——共用体union

从结果再次验证共用体实质:共享一块内存,只是解析方式不同。


总结

通过以上分析,我们了解了共用体的实质,如何定义和使用,以及与结构体的区别等。 之后我会讲解共用体的应用场景,尤其是如何使用共用体测试系统大小端,这也是面试和笔试中出现的概率非常高的题目,如果你对大小端的概念不是很清楚,或者想了解有哪些办法测试系统的大小端,欢迎转发,收藏,关注,后续内容更加精彩哟~


C语言学习篇(23)——共用体union


分享到:


相關文章: