1、为什么要内存对齐
许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4,8,或16)的倍数。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。(来源于计算机操作系统第一部分,程序结构和执行)
其中详细解释参考大神博客
https://blog.youkuaiyun.com/lgouc/article/details/8235471
2、内存对齐方法
1、利用默认规则
例1:
//代码1 共占用 2 * sizeof(int) 个字节
struct test
{
char a;
char b;
int num;
}
代码1 变量内存分布如图
默认情况下以 int 型变量 的字节数(K值)进行对齐。
因为char 类型只占用了一个字节,所以占1格,int 型需要4个字节,占了4格
所以整个test 结构体需要8个字节。
例2:
//代码2 共占用 3 * sizeof(int) 个字节
struct test
{
char a;
int num;
char b;
}
代码2 变量内存分布如图
总结:
在使用gcc编译时,由于结构体内变量按照先后顺序在内存中存储,且按照默认规则以4进行对齐。要求结构体起始地址必须是4的倍数。在存储char 类型后,存储int 类型时仍然要求起始地址为4的倍数,所以char 类型后的地址就不满足存储要求。所以char 类型后空了3个字节。所以这就是代码2的内存分布原因。
代码1的在存储char类型后,下一个还是char类型。只有一个字节,所以第一个char 类型后的地址是满足存储要求的。所以分布如代码1下的图示所示。
实例展示
类似的代码和实际运算结果展示:
#include <stdio.h>
struct ffe
{
char a; //占用4个字节 实际使用1个字节
int b; //占用4个字节 实际使用4个字节 ,和char 变量共用8个字节
double c; //占用8个字节 实际使用8个字节
};
int main()
{
ffe tr;
printf("%d\n",sizeof(tr));
printf("a = %p\n, b = %p\n, c = %p\n",&tr.a, &tr.b, &tr.c);
return 0;
}
输出结果:
16 a = 0x7ffcbf14ca00 , b =0x7ffcbf14ca04, c = 0x7ffcbf14ca08
内存中分布大概如图示
当变量位置交换后,实际占用内存为 3 * sizeof(double)。 一般为24字节。
struct ffe
{
char a; //占用8个字节,实际使用1个字节
double c; //占用8个字节,实际使用8个字节
int b; //占用8个字节,实际使用4个字节
};
所以根据变量字节大小来决定变量顺序可以有效减小使用内存。
2、设置结构体内存对齐K值
使用如下语句
#pragma pack(k) //没有分号
对应代码1,如果将k 设置为1,则实际内存占用为6个字节。将K设置为2 ,结果也是一致的。因为设置了变量的变量的对齐为2的倍数。但整型变量实际占用了两个内存单元。会造成取值速度下降。
K = 4 的结果与代码1的结果是一致的。
当K = 8 超过了默认的 K =4 (int 4个字节)。内存对齐仍然是按照 K = 4 进行内存对齐的。
3.位域
位域:描述的是结构体中变量占的位。(一个字节共有8个位)
char 类型,一个字节 ,共有8个位,所以位域数不能超过8。
int 类型 ,4个字节,共有32个位,所以位域数不能超过32。
具体使用
//代码3
struct ffe
{
char a:2;
char b:2;
char c:2;
};
由于一个字节(8个位)是系统中最小可寻址单位。所以无法对位域取地址。无法通过地址查看相关关系。
代码3的结构体,占用内存为一个字节。由于一个字符变量只使用了两个位,表示的最大字符ASCII码不能超过3;因为超过了就会溢出。
但是变量仍然满足内存对齐规则。代码4的结构体仍然占据8个字节。
//代码4
struct ffe
{
char a:2;
char b:2;
char c:2;
int d:1;
};
只能通过大小或者赋值去判断。
4、位域的联合使用
struct ttr
{
int a:8;
int b:8;
int c:8;
int d:8;
};
union ffe
{
int a;
ttr ch;
};
整型变量a 和结构体变量ch 共同占据同一块内存,两种类型的内存大小是一致的,地址是一致的。访问变量按照正常访问即可,所以可以通过两种类型访问同一块内存 。将较小的值限制在相应的小空间内。达到节约内存的目的。所以赋值不能超过实际的位。