3. 自定义类型****
目录
1. 内存对齐(必考)
如何计算?
为什么要内存对齐?
2. 联合
2.1 联合的定义
2.2 联合的特点
1. 内存对齐(必考)
结构体内存对齐是一个特别热门的考点。
如何计算?
- 第一个成员在与结构体偏移量为0的地址处
- 其他成员变量要对齐到对齐数的整数倍的地址处,对齐数=编译器默认的一个对齐数 与 该成员大小的较小值
- 结构体总大小为最大对齐数的整数倍,每个成员变量都有一个对齐数
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套的)的整数倍。
举几个例子:
struct s1
{
char c1;
int i;
char c2;
};
printf("%zu\n", sizeof(struct s1)); 12
内存图:
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| c1 | 填 | 填 | 填 | i (4 字节) | c2 | 填 | 填 | 填 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
-
c1
(1 字节):位于偏移量0
。占用 1 个字节。 -
为了对齐
int i
,编译器在c1
后插入了 3 个填充字节。int的大小是4,默认对齐数是8,他们的较小值是4,所以 i 要对齐到对齐数也就是4的整数倍的地址(4,8,12,16...) -
c2,对齐数是1,1的整数倍8就可以放。
-
总大小为最大对齐数的整数倍,char是1, int是4,最大对齐数是4,对齐到4的整数倍12。
struct s2
{
char c1;
char c2;
int i;
};
printf("%zu\n", sizeof(struct s2)); 8
+-----+-----+-----+-----+-----+-----+-----+-----+
| c1 | c2 | 填 | 填 | i (4 字节) |
+-----+-----+-----+-----+-----+-----+-----+-----+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+-----+-----+-----+-----+-----+-----+-----+-----+
struct s3
{
double d; 8
char c; 1
int i; 4
};
printf("%zu\n", sizeof(struct s3)); 16
偏移量 内容
----- -----
0 d (字节 0)
1 d (字节 1)
2 d (字节 2)
3 d (字节 3)
4 d (字节 4)
5 d (字节 5)
6 d (字节 6)
7 d (字节 7)
----------------------
8 c
----------------------
9 填充
10 填充
11 填充
----------------------
12 i (字节 0)
13 i (字节 1)
14 i (字节 2)
15 i (字节 3)
----------------------
struct s4
{
char c1; 1
struct s3 s3; 8
char c2; 1
};
printf("%zu\n", sizeof(struct s4)); 32
s3的大小是16,默认对齐数是8,对齐数是较小值 = 8。从8的整数倍--8开始。
偏移量 内容
----- -----
0 c1
1 填充
2 填充
3 填充
4 填充
5 填充
6 填充
7 填充
----------------------
8 s3
9 s3
10 s3
11 s3
.....
23 s3
----------------------
24 d
25 d
26 d
27 d
.....
31 d
d的对齐数是8,从8的倍数开始,3*8=24,所以从24开始到31.
总大小为最大对齐数8的整数倍,4*8=32
为什么要内存对齐?
1.平台原因:
不是所有硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次访问。
比如,一次访问4个字节,但是没有对齐,一个int可能上面存了2字节,下面存了两个字节,正好被访问从中间截断了,需要访问两次。
总体来说:
结构体的内存对齐是拿空间换时间的做法。所以在涉及结构体时,要让占用小的成员尽量集中在一起,就像前面的s1和s2,成员一摸一样,但是占用空间不同。
2. 联合
2.1 联合的定义
联合是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用一块空间(所以联合也叫共用体)
union Un
{
char c;
int i;
};
union Un un;
printf("%zu\n", sizeof(un)); 4
我们发现c和i共用了空间,大小为4。
2.2 联合的特点
成员共用一块内存空间,后存的会把先存的覆盖掉,一个联合变量的大小,至少是最大成员的大小(至少得有能力保存最大的那个成员),当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
union Un2
{
int i;
char c;
};
union Un2 un2;
int main()
{
printf("%zu\n", sizeof(un2)); 4
printf("%p\n", &(un2.i)); 00007ff7626ff034
printf("%p\n", &(un2.c)); 00007ff7626ff034
un2.i = 0x12345678;
printf("%x\n", un2.i); 12345678
un2.c = 0x00000012;
printf("%x\n", un2.i); 12345612
}
由于联合体的成员共享同一块内存,因此 un2.i
和 un2.c
的地址是相同的
-
首先将
un2.i
赋值为0x12345678
。 -
然后将
un2.c
赋值为0x00000012
。
由于联合体的成员共享内存,修改 un2.c
会覆盖 un2.i
的第一个字节(最低字节)
-
由于
un2.c
修改了un2.i
的第一个字节,un2.i
的值会发生变化。 -
具体分析:
-
初始值:
un2.i = 0x12345678
。 -
修改
un2.c
后,un2.i
的第一个字节被覆盖为0x12
。 -
最终值:
un2.i = 0x12345612
。
-
-
输出为 12345612。
这里就可以引出大小端存储
面试题:判断当前计算机的大小端存储
可以用联合解决:
union Un
{
char c;
int i;
}un;
int main()
{
un.i = 1;
if(un.c ==1)
printf("小端""\n");
else
printf("大端""\n");
return 0;
}
小端序(Little Endian)
-
低字节存储在低地址,高字节存储在高地址。
-
un.i = 1
的内存布局:
地址 值
---- ----
0x00 01 // 低字节
0x01 00
0x02 00
0x03 00 // 高字节
un.c
访问的是 0x00
地址的字节,值为 1
,因此 un.c == 1
为真。
大端序(Big Endian)
-
高字节存储在低地址,低字节存储在高地址。
-
un.i = 1
的内存布局:
地址 值
---- ----
0x00 00 // 高字节
0x01 00
0x02 00
0x03 01 // 低字节
un.c
访问的是 0x00
地址的字节,值为 0
,因此 un.c == 1
为假。