目录
- 结构体
结构体内存对齐
修改默认对齐数
结构体传参
- 位段
- 枚举
- 联合(共用体)
1.结构体内存对齐
我们先定义一个结构体
S1,S2的大小是多少呢,是一样的吗?如果你认为c1占1个字节,i占4个字节,c2占1个字节,加起来是6个字节,运行起来的话你就发现不是这样,而且S1,S2的大小也不一样。
为什么会这样呢?我们就要研究一个东西:offsetof,它可以计算结构体成员相较于结构体起始位置的偏移量。使用这个宏需要的头文件是stddef.h,我们计算一下每个成员的偏移量:
如果把它画出来就是这样的:
所以中间为什么会浪费6个字节呢?我们要讲一下结构体内存对齐的规则:
1.结构体的第一个成员永远放在相较于起始位置偏移量为0的地方。
2.从第二个成员开始,往后的每一个成员都要对齐到某个对齐数的整数倍处。
对齐数:结构体成员自身的大小和默认对齐数的较小值。
vs上默认对齐数是8。gcc没有默认对齐数,对齐数就是结构体成员自身的大小。
3.结构体的总大小必须是最大对齐数的整数倍。
最大对齐数是所有成员的对齐数的最大值。
所以,解释一下就是这样:
按照上面的规则,我们看一下S2的大小为什么是8。
总结:结构体内存对齐是拿空间换取时间的做法。
对于S1,S2这样的例子,我们还能发现:让占用空间小的成员尽量集中在一起会节省空间。
修改默认对齐数
使用 #pragma 这个预处理指令可以修改默认对齐数
可以看到S1的大小变为了6。
结构体传参
可以看到传值和传址都能达到我们想要的效果,那么哪种方式更好呢?当然是传址。
原因:
- 函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。
- 如果传递结构体过大的话,参数压栈的系统开销会比较大,会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
2.位段
位段的声明和结构体是类似的,有两个不同:
- 位段的成员必须是int,unsigend int,sigend int
- 位段的成员名后面有一个冒号和一个数字。
A就是一个位段类型。
为什么是8?
其实位段的位表示二进制位, 2+5+10+30=47,一共47个字节,开辟8个整形就够了。
位段的内存分配
- 位段的成员可以是int,unsigend int,sigend int或char类型。
- 位段的空间是按需要以4个字节(int)或1个字节(char)开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用也是不确定的。
总结:
跟结构相比,位段可以达到相同的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
3.枚举
顾名思义就是一一列举,把可能的取值一一列举。
比如:一周有七天,可以一一列举;一年十二个月,也可以一一列举。
枚举常量是有值的,默认从0开始,一次递增1,也可以在声明枚举类型的时候赋初值。
枚举优点:
- 增加代码的可读性和可维护性。
- 和#define定义的标识符比较枚举有类型检查,更严谨。
- 便于调试。
- 使用方便,一次可以定义多个常量。
4.联合
联合也是一种特殊的自定义类型。
这种类型定义的变量也包含一系列的成员,特征是这些成员公用一块空间(所以联合体也叫共用体)。
答案是4,我们来探索一下:
我们先看一下它们的地址
可以看到它们地址都是一样的。
联合的特点:
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小。
改i的值就会改c的值。
所以判断大小端我们就可以这样写:
union un
{
char c;
int i;
};
int main()
{
union un u = { 0 };
u.i = 1;
if (u.c == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
这里就和上面讲的结构体内存对齐类似了,主要注重联合。
例如: