目录
结构体(struct)
结构体(struct)是程序员自己设定的数据类型, 里面可以包括很多种不同的数据类型, 像是int, double, 指针, 数组等等. 这里主要介绍结构体内存规则以及位段.
结构体内存规则
先来看一段代码:
#include <stdio.h> struct S1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct S1)); return 0; }
正常的思维会以为这个结构体的大小就是两个char类型加一个int类型(1 + 4 + 1 = 6), 实际上并不是的, 答案是12.
这个12是怎么算出来的? 结构体在内存中不是连续存放的(所以上面的答案不是1 + 4 + 1 = 6), 而是有一定的对齐规则的.
对齐规则
1.结构体第一个成员永远放在相较于结构体变量起始位置的偏移量为0的位置(说白了就是开头就是第一个成员).
2.从第二个成员开始, 往后的每个成员都要对齐到他自己的对齐数的整数倍处.
3.对齐数是结构体成员自身的大小和默认对齐数的较小值. VS上默认对齐数是8, gcc没有默认对齐数, 对齐数就是结构体成员自身的大小.
4.结构体的总大小, 必须是所有结构体成员中最大对齐数的整数倍. 注意:序号是从0开始, 每个成员对齐自己的对齐数的整数倍的序号, 但是内存是要多1的(比如序号0其实占内存1).
5.如果存在结构体嵌套, 嵌套的结构体对齐他自身所有成员中最大对齐数的整数倍上, 然后整个结构体的大小就是所有成员对齐数(包括这个被嵌套的结构体的对齐数)中最大对齐数的整数倍.
6.如果结构体成员中出现数组, 就直接往下放:
struct S1 { char c1; int i[2]; char c2; }; 等同于: struct S1 { char c1; int a; int b; char c2; };
上面那个结构体内存为12的内存演示图:
为什么要有内存对齐
原因如下:
1.平台原因(移植问题): 不是所有的硬件平台都能访问任意地址上的任意数据, 某些平台只能在某些地址取某些特定类型的数据, 否则抛出硬件异常.
2.性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐.原因在于为了访问未对齐的内存, 处理器需要做两次内存访问, 而对齐的内存访问只需一次(比如32位机器一次读写32bit就是4字节, 不对齐假如一个char接一个int, 想读取int一次读取只能读取到i的3个字节, 还得多读取一次i剩下的最后一个字节).
总的来说结构体对齐就是空间换时间, 所以我们设计结构体时, 尽量让小的变量集中在一起.
#pragma pack()
#pragma pack()可以修改默认对齐数, 不放参数就是默认, 放了参数是几默认对齐数就是几.
#pragma pack(1) struct s { char c1; int a; char c2; }; // 更改了默认对齐数为1, 相当于不用对齐, 挤着放, 所以上面的内存大小是6.
一般情况下不要乱修改默认对齐数, 比如修改成3这种.
位段
直接看代码:
#include <stdio.h> struct S1 { int _a : 2; // 位段, 后面的数字代表占几个bit位. int _b : 5; int _c : 10; int _d : 20; }; int main() { printf("%d\n", sizeof(struct S1)); // 答案是8. return 0; }
位段内存的分配
1.位段的成员可以是int, unsigned int, signed int, char(后来引入的)等(整型的类型才行).
2.位段的空间上是按照需要以4个字节(int)或1个字节(char)等方式来开辟(不够再加, 够了不用加).
3.位段涉及很多不确定的因素, 位段是不跨平台的, 注重可移植性的程序应该避免使用位段(C语言位段的规定太泛了不够细: 例如开辟一个4字节的int,没有规定从低地址给空间给字段还是高地址, 字段不够的时候, 申请多了一个int, 前面第一个int中剩下的bit位还要不要使用还是直接用新开辟的32个bit位, C语言都没有规定, 下面单独补充原因).
在VS里, 字段从低位向高位申请空间, 如果剩下空间字段不够直接不要了, 单独申请个新的空间用(比如int用了30字段剩下2字段, 然后还有10个字段待分配, 剩下那2个字段直接放弃不要开辟新的32个字段使用).
位段的跨平台问题
1.int位段被当成有符号数还是无符号数是不确定的.
2.位段中的成员在内存中从左向右分配还是从右向左分配标准没有定义(VS从右到左).
3.位段中最大位的数目不能确定(16位机器最大是16, 32位最大是32, 位段给27的话, 16位机器就会出现问题).
4.当一个结构包含两个位段, 第二个位段成员比较大, 无法容纳于第一个位段剩余的位时, 是舍弃剩余的位还是利用没有确定(VS中是直接舍弃).
总结: 跟结构相比, 位段可以达到同样的效果, 并且可以很好的节省空间, 但是有跨平台的问题存在.
联合体(union)
union Un { char c; int i; }; // 联合体占同一块空间, 根据使用的变量不同决定获取空间内多少个字节. union Un x = {0}; x.i = 0x11223344; x.c = 0x55; // 一开始空间是44 33 22 11, 后面改变c就变成55 33 22 11. // 此时输出i就是0x11223355, 输出c就是0x55. // 联合体空间大小的计算: // 联合的大小至少是最大成员的大小. // 当最大成员大小不是最大对齐数的整数倍, 要对齐到最大对齐数的整数倍. // 例如: union Un1 { char c[5]; // 5 int i; // 4 }; // 此时按理来说大小应该是5, 但是为了对齐最大对齐数(int是4)的整数倍, 所以大小是8.
利用联合体的特性, 也可以验证当前平台下是小端存储还是大端存储:
#include <stdio.h> int check() { union { int i; char c; } un = {.i = 1}; // 匿名联合体. return un.c; } int main() { int res = check(); if (res == 1) printf("小端\n"); else printf("大端\n"); return 0; }