C语言的结构体及内存对齐
结构体基础
结构体就是一些成员的集合,结构体的每一个成员可以是整型、数组、指针、结构体等不同的类型。
下面是一个简单的结构体结构,包含了类型声明struct Stu
、成员、结构体变量s1
的声明。
1 | struct Stu { //类型 |
我们可以像上面那样声明一个结构体变量,也可以像下面这样单独声明。
1 | struct Stu s2; |
struct
是结构体关键字,Stu
是结构体标志,两者构成了结构体类型。上面的语句表示为struct Stu
类型的结构体声明了一个变量s1
。下面是对s1的赋值操作,可以在声明结构体变量的时候直接赋值。
1 | struct Stu s1 = { "panghutx",20,"male" }; |
在声明结构体时,我们可以对结构体不完全声明。
1 | struct{ |
以上就不完全声明了两个结构体,我们称之为匿名结构体类型。结构体变量a和*p具有相同的成员,但它们是两个完全不同的类型。当我们尝试如下代码时,会出现警告。
1 | *p = &a; |
说到结构体,我们难免提到一个关键字typedef
,用于定义新的类型(或类型重命名)。我们在学习链表时可能会看到这样的结构,下面这段代码是对struct Node
重命名为Node
.而且还在结构体中引用了自己。
1 | typedef struct Node |
切记在结构体自引用时不要使用匿名结构体,否则就是在定义新类型的时候引用了新类型,这是错误的。
再看下面的写法,定义了两个新类型,Node
和*pNode
,Node我们已经知道是对struct Node
进行重命名,而*pNode
是对struct Node*
的重命名。
1 | typedef struct Node |
结构体内存对齐
结构体的大小不是单纯的各元素相加,因为主流计算机使用的是32bit字长的CPU,那么取4个字节数要比1个高效,所以结构体存在内存对齐。每个编译器都有自己的对齐系数,程序员也可以通过预编译命令来改变默认对齐数。
1 |
对齐规则:
①首个成员放在0ffset(偏移量)为0的位置,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数
= 编译器默认的一个对齐数 与 该成员大小的较小值。
②结构体总大小为各元素最大对齐数的整数倍。
举个例子,计算下面结构体的大小是多少。
1 | struct S1 |
假设编译环境默认4字节对齐。
c1是结构体首个元素,直接放到偏移量为0的位置,占1个字节;i自身大小为4字节,默认对齐4字节,因此对齐数就是四字节,将其放到对齐数整数倍的位置,也就是4偏移量的位置。c2自身大小1字节,默认对齐数4,因此对齐数是1,将其放到对齐数整数倍的位置,也就是int的后面。
0~8偏移量,那么该结构体为9个字节,对吗?别忘了规则②,结构体总大小是各元素最大对齐数的整数倍。结构体内最大对齐数的元素是int,对齐数是4,9不是4的整数倍,再开辟3个字节。
综上该结构体大小为12字节。
结构体位段
c语言允许在一个结构体中以位为单位来指定成员长度,利用位段能够节约空间。
1 | struct A |
A就是1个位段,它的大小为8个字节,想知道为什么是8个字节,要知道它的内存分配。
- 位段的成员可以是
int
unsigned int
signed int
或者是char
(属于整形家族)类型 - 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
调试下面代码,我们可以看一下空间是如何开辟的。
1 | struct S |
我在vs2019环境下调试,和vs2013结果一样。先开辟一字节,从低位开始存数据,存不下时舍弃剩余位,再开辟一字节空间。
以上存储方式只能代表vs环境下,其他环境不确定。要知道,位段的内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。