我们都知道计算机是以字节(Byte)为单位划分的,但是大部分处理器并不是按字节块来存取内存的。它一般会以2字节,4字节,8字节,16字节甚至32字节为单位来存取内存。
32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte?实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。下面我们来介绍什么是内存对齐?
对齐准则
1、char 1 字节对齐 ,即存放 char 型的变量,内存单元的编号是 1 的倍数即可。
2、short int 2 字节对齐 ,即存放 short int 型的变量,起始内存单元的编号是 2 的倍数即可。
3、int 4 字节对齐 ,即存放 int 型的变量,起始内存单元的编号是 4 的倍数即可
4、long int 在 32 位平台下,4 字节对齐 ,即存放 long int 型的变量,起始内存单元的编号是 4 的倍数即可 。
5、float 4 字节对齐 ,即存放 float 型的变量,起始内存单元的编号是 4 的倍数即可
6、double
vc 环境下, 8 字节对齐,即存放 double 型变量的起始地址,必须是 8 的倍数,double 变量占 8 字节
gcc 环境下, 4 字节对齐,即存放 double 型变量的起始地址,必须是 4 的倍数,double 变量占 8 字节。
在32位和64位的机器上,size_t的大小不同,如果在centos7上运行32位程序,我们需要安装
yum -y install gcc-multilib g+±multilib
通过一个小例子来说明是如何对齐的。
#include <stdio.h>
struct
{
char a;
int b;
char c;
}s1;
/*
gcc -m32 test.c -o test
g++ -m32 test.cpp -o test
*/
int main(int argc, char const *argv[])
{
printf("%d\n",sizeof(s1));
return 0;
}
运行:
编译成32位程序并运行,32位默认4字节对齐,可以看到,结构体test 的大小为12字节(a占1字节,b占4字节,c占1字节),而不是6字节。
下面下图:
上面的结构体,我们是不是可以考虑这样写:
struct
{
int a;
char b;
char c;
}s1;
结构体变量大小是,它所有成员之和。 因为结构体变量是所有成员的集合。结构体变量分配内存的时候,是规则的。
给结构体变量分配内存的时候,会去结构体变量中找基本类型的成员哪个基本类型的成员占字节数多,就以它大小为单位开辟内存,
在 gcc 中出现了 double 类型例外
1、成员中只有 char 型数据 ,以 1 字节为单位开辟内存。
2、成员中出现了 short int 类型数据,没有更大字节数的基本类型数据。
以 2 字节为单位开辟内存
3、出现了 int float 没有更大字节的基本类型数据的时候以 4 字节为单位开辟内存。
4、出现了 double 类型的数据
情况 1:在 vc 里,以 8 字节为单位开辟内存。
情况 2:在 gcc 里,以 4 字节为单位开辟内存。 无论是那种环境,double 型变量,占 8 字节。
注意: 如果在结构体中出现了数组,数组可以看成多个变量的集合。 如果出现指针的话,没有占字节数更大的类型的,以 4 字节为单位开辟内存。 在内存中存储结构体成员的时候,按定义的结构体成员的顺序存储。
啊哈!这里忘了说,使用union时,要留意平台的大小端问题。
百度百科——大小端模式
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
怎么获知自己使用的平台的大小端?Linux有个方法:
#include <stdio.h>
static union
{
char c[4];
unsigned long l;
}endian_test = { { 'l', '?', '?', 'b' } };
#define ENDIANNESS ((char)endian_test.l)
int main(int argc, char const *argv[])
{
printf("ENDIANNESS: %c\n", ENDIANNESS);
return 0;
}
编译运行:
可以看出我的虚拟机是小端。
为什么要内存对齐?
从上面的结构体来看,本来只需要6字节的空间,最后却占用了12字节,很明显浪费了空间。为什么要内存对齐,简单的说内存对齐能够提高 cpu 读取数据的速度,减少 cpu 访问数据的出错性。
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放。
#pragma pack的作用
#pragma pack 的主要作用就是改变编译器的内存对齐方式。不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n= 1,2,4,8,16来改变对齐系数。
#include <stdio.h>
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char a;
int b;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
int main(int argc, char const *argv[])
{
printf("%d\n", sizeof(struct D));
return 0;
}
运行:
运行结果是7,上面的例子,应该对内存对齐有了全面的认识和了解,在以后的编码中定义结构体时需要考虑成员变量定义的先后顺序了。
总结
为了速度和正确性,请对齐你的数据。
1、结构体的内存对齐,按照其内部最大元素基本类型或者模数大小对齐。
2、模数在不同平台值不一样,也可通过#pragma pack(n)方式去改变。
3、结构体的内存大小,并非其内部元素大小之和。
欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下: