在掌握了结构体的基本使用后。
现在我们深入讨论一个问题: 计算结构体的大小。
计算结构体大小
如何计算结构体的大小:
首先得掌握结构体的对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处对齐数 = 编译器默认的一个对齐数 与 该成员大小两者的较小值。
VS中默认的值为8 ,Linux中的默认值为4 - 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
计算结构体大小示例:
//练习1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
// 12
//练习2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
// 8
//练习3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
// 16
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4))
// 8, 16, 8 === 32
为什么存在内存对齐?
大部分的参考资料都是如是说的:
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址 处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理 器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
//例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别
修改默认对齐数
#include <stdio.h>
#pragma pack(8)
//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()
//取消设置的默认对齐数,还原为默认
#pragma pack(1)
//设置默认对齐数为8
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()
//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1)); // 12
printf("%d\n", sizeof(struct S2)); // 6
return 0;
}
如何知道结构体中某个成员相对于结构体起始位置的偏移量
offsetof(StructName, MunberName);// offsetof是个宏
联合体
“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。如下面介绍的“单位”变量,如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符型(教研室)。要么赋予整型值,要么赋予字符型,不能把两者同时赋予它。<百度百科>
这段文字基本能解释什么是联合体
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un)); // 4
联合体的特点: 联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得 有能力保存最大的那个成员)。
什么是大端和小端
- Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
- Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:
- 大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
- 小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
可见,大端模式和字符串的存储模式类似。
例如:
union Un
{
int i;
char c;
};
union Un un;
// 下面输出的结果是一样的吗?
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
// 相同
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
// 11223355
大端小段的应用场景
主要运用在网络协议中
联合体大小计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
}; //下面输出的结果是什么?
short c;
printf("%d\n", sizeof(union Un1)); // 8
printf("%d\n", sizeof(union Un2)); // 16
完.