结构体
谈到结构体,我们会关注结构体的声明,结构体的自引用和结构体的大小等问题。接下来我们主要来分析一下结构体的大小是如何计算的,而结构体的大小又是与结构体中的内存对齐息息相关,我们通过对结构体中的内存对齐进行分析来得到结构体的大小。
结构体中的内存对齐
结构体中的内存对齐原则:
- 第1个成员在与结构体变量偏移量为0的地址处。
- 从第2个成员开始,就要对齐到“对齐数”的整数倍的地址处了
- 对于嵌套了结构体的结构体,嵌套的结构体要对齐到 自己的最大对齐数 的整数倍处
- 结构体的总大小是 最大对齐数 的整数倍(包含嵌套结构体的对齐数)
对齐数:对齐数 = 编译器默认的对齐数 与 该成员大小的较小值(VS中默认对齐数为8,Linux中无默认对齐数)
偏移量的计算
我们可以使用offset宏去计算结构体成员的偏移量。
size_t offsetof( 结构体名称, 成员变量 );
offsetof宏的模拟实现:
我们可以以0为地址,将其强转为结构体类型,然后去访问它的成员变量,因为偏移量 = 成员变量的地址 - 结构体起始位置的地址就是它的偏移量,此时由于结构体的起始地址是0,因此成员变量的地址就是该成员变量的偏移量,最后再进行(int)强转即可。
#define OFFSETOF(struct_name,member_name) (int)&(((struct_name*)0)->member_name) struct S { char a; int b; short c; }; int main() { int ret = OFFSETOF(struct S, b); printf("%d\n", ret); return 0; }
修改默认对齐数
我们可以使用 #pragma 预处理指令去改变默认对齐数。(一般我们会将默认对齐数改为2^n)
//此时下面的结构体就是以编译器的默认对齐数为4来进行内存对齐的
#pragma pack(4)
struct S
{
char a;
int b;
char c;
};
//设置过后要进行取消
#pragma pack()
结构体的传参
结构体传参的时候,要传结构体的地址。
原因:
- 函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。
- 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销会比较大,这会导致性能的下降。
位段
位段与结构体是类似的,但是存在2点不同
- 位段的成员必须是整形家族:int (unsigned int, signed int) , char, short【不能存在float, double】
- 位段的成员名后要加 : 和数字
形如:
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
位段中的内存分配
位段在开辟空间时候,是按照1个字节 ( char ) 或者4个字节 ( int ) 的方式去开辟的。
但是由于位段存在跨平台问题,所以有很多不确定因素。
位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的
- 位段中最大位的数目不能确定 (16位机器最大是16,32位机器最大32,32位机器上的20在16位机器上会出问题)
- 位段中的成员在内存中从左向右分配还是从右向左分配没有定义
- 当一个结构体有2个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余位,还是利用,无法确定。
枚举
枚举的使用
枚举的优点
- 使用方便,一次可以定义多个变量
- 便于调试
- 防止命名污染
- 增加了代码的可读性和可维护性
- 与#define定义的标识符相比,枚举有类型的检查,更加严谨
联合
联合的成员是共用同一块内存空间的,因此对于一个联合变量的大小,它至少是最大成员的大小。
union UN
{
int i;
char c;
};
注意:联合的成员i和c是无法同时使用的。
联合大小的计算
- 联合的大小至少是最大成员的大小
- 当最大成员的大小不是最大对齐数的整数倍时,要对齐到最大对齐数的整数倍
用联合去判断大小端存储
通过联合体中的int向其存入整形1,然后再通过联合体的char去访问,这样如果是小端存储的话,就会访问到01,如果是大端就是00
union UN
{
int i;
char c;
};
int check_sys()
{
union UN u;
u.i = 1;
return u.c;
}
int main()
{
if (check_sys())
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}