c/c++:内存对齐详解

目录

一、内存对齐原因

二、如何内存对齐

三、内存对齐案例

3.1 对齐模数:8

3.2 对齐模数:4

3.3 实践说明

四、32位机和64位机内存对齐的区别


 

 

一、内存对齐原因

我们知道内存的最小单元是一个字节,当cpu从内存中读取数据的时候,是一个一个字节读取,所以内存对我们应该是入下图这样:

但是实际上cpu将内存当成多个块,每次从内存中读取一个块,这个块的大小可能是2、4、8、16等。

那么下面,我们来分析下非内存对齐和内存对齐的优缺点在哪?

内存对齐是操作系统为了提高访问内存的策略。操作系统在访问内存的时候,每次读取一定长度(这个长度是操作系统默认的对齐数,或者默认对齐数的整数倍)。如果没有对齐,为了访问一个变量可能产生二次访问。

 

  • 至此大家应该能够简单明白,为什么要简单内存对齐?
  1. 提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
  2. 某些平台只能在特定的地址处访问特定类型的数据,否则抛出硬件异常给操作系统。

 

二、如何内存对齐

 

  • 对于标准数据类型,它的地址只要是它的长度的整数倍。
  • 对于非标准数据类型,比如结构体,要遵循一下对齐原则

1. 数组成员对齐规则。第一个数组成员应该放在offset为0的地方,以后每个数组成员应该放在offset:

min(当前成员的大小,#pargama pack(n))整数倍的地方开始,win#pargama pack(n)默认为8

比如int在32位机器为4字节,#pargama pack(2),那么从2的倍数地方开始存储)。

 

2. 结构体总的大小,也就是sizeof的结果,必须是min(结构体内部最大成员,#pargama pack(n))的整数倍,不足要补齐。

 

3. 结构体做为成员的对齐规则。如果一个结构体B里嵌套另一个结构体A,还是以最大成员类型的大小对齐:

min{ 结构体A的起点为A内部最大成员,#pargama pack(n) }

(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。

手动设置对齐模数:

    1. #pragma pack(show)

显示当前packing alignment的字节数,以warning message的形式被显示。

    1. #pragma pack(push)

将当前指定的packing alignment数组进行压栈操作,这里的栈是the internal compiler stack,同事设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数组压栈。

    1. #pragma pack(pop)

从internal compiler stack中删除最顶端的reaord; 如果没有指定n,则当前栈顶record即为新的packing alignement数值;如果指定了n,则n成为新的packing alignment值

    1. #pragma pack(n)

指定packing的数值,以字节为单位,,合法的数值分别是1,2,4,8,16。

Linux 默认#pragma pack(4) 
window 默认#pragma pack(8) 

 

内存对齐总结:

第一个属性开始  从0开始计算偏移量。
第二个和之后属性要放在  该属性的大小 与 对齐模数比  取小的值的 整数倍上。 
当所有属性都计算完毕后,整体做二次偏移,
将上面计算的结果 扩充到 这个结构体中最大数据类型 与对齐模数  比 取小的值 的整数倍。

 

 

三、内存对齐案例

所以案例均是 WIN 64位机上运行。

3.1 对齐模数:8

#pragma pack(8)

 

typedef struct _STUDENT{

    int a;

    char b;

    double c;

    float d;

}Student;

 

typedef struct _STUDENT2{

    char a;

    Student b;

    double c;

}Student2;

 

void test01(){

 

    //Student

    int a;          0~3     

    char b;       4~7     (4的由来:min{(char 字节数)1,(对齐模数)8}  float b的开始地址是1的整数倍)

    double c;   8~15   (8的由来:min{(double 字节数)8,(对齐模数)8} double c的开始地址本是8的整数倍,所以c就从8开始,就可以推出 char b 所占字节数是4~7 

    float d;       16~20    (16的由来:min{(float 字节数)8,(对齐模数)8}  float d的开始地址是8的整数倍)

 

二次偏移:将上面计算的结果 扩充到min{(结构体中最大数据类型)8,(对齐模数)8}   既为8的整数倍,最后结果是24

 

    printf("sizeof Student:%d\n",sizeof(Student));


    //Student2

 

    //a从偏移量为0位置开始

    char a;        0~7      (7的由来:由Student b结构决定

    Student b;  8~31    (8的由来:min{(结构体Student 的起点为Student 内部最大成员)8,(对齐模数)8 }的倍数,就是8,就可以推出char a所占位置为0~7。) 

    double c;    32~39    (32由来:min{(double 字节数)8,(对齐模数)8}  double的开始地址是8的整数倍,之前我们算出Student b占24字节,所以8~31都是Student b占的,所以才是32

 

二次偏移:将上面计算的结果 扩充到min{(结构体中最大数据类型)8,(对齐模数)8}   既为8的整数倍,最后结果是40

    printf("sizeof Student2:%d\n", sizeof(Student2));

}

 

3.2 对齐模数:4

#pragma pack(4)      

 

typedef struct _STUDENT{

    int a;       

    char b;       

    double c;    

    float d;       

}Student;

 

typedef struct _STUDENT2{

    char a;

    Student b;

    double c;

}Student2;

 

void test01(){

 

    //Student

     int a;           0~3

     char b;        4~7        (4的由来:min{(char 字节数)1,(对齐模数)4}  float b的开始地址是1的整数倍)

     double c;    8~15      (8的由来:min{(double 字节数)8,(对齐模数)4} double c的开始地址本是4的整数倍,但是4是被b站了,所以是4的最小倍数8,所以c就从8开始。就可以推出 char b 所占字节数是4~7 

      float d;        16~19    (16的由来:min{(float 字节数)8,(对齐模数)4}  float d的开始地址是4的整数倍)

 

二次偏移:将上面计算的结果 扩充到min{(结构体中最大数据类型)8,(对齐模数)4}   既为4的整数倍,最后结果是20

    printf("sizeof Student:%d\n",sizeof(Student));


 

    //Student2

    //a从偏移量为0位置开始

    char a;        0~3      (3的由来:由Student b结构决定

    Student b;  4~23    (4的由来:min{(结构体Student 的起点为Student 内部最大成员)8,(对齐模数)4 }的倍数,就是4,就可以推出char a所占位置为0~3。) 

    double c;    24~31    (32由来:min{(double 字节数)8,(对齐模数)8}  double的开始地址是8的整数倍,之前我们算出Student b占24字节,所以8~31都是Student b占的,所以才是32

 

二次偏移:将上面计算的结果 扩充到min{(结构体中最大数据类型)8,(对齐模数)8}   既为8的整数倍,最后结果是32

    printf("sizeof Student2:%d\n", sizeof(Student2));

}

 

3.3 实践说明

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

#pragma pack(16)   // 16 8 4 2 1

struct Student
{
	int a;      
	char b;   
	double c; 
	float d;  
};

struct Student2
{
	char a;  
	struct Student b; 
	double c;  
};

void test()
{
	printf("#pragma pack(16)\n");
	printf("%d\n", offsetof(struct Student2, a));
	printf("%d\n", offsetof(struct Student2, b));
	printf("%d\n", offsetof(struct Student2, c));

	printf("%d",sizeof(struct Student2));
}

结果:

 

带结构体的内存对齐:

对齐模数: 16 8 4 2 1
char a 0~7 0~7 0~3 0~1 0
Student b; 8~31 8~31 4~23 2~19 1~17
double c; 32~39 32~39 24~31 20~27 18~25
总大小: 40 40 32 28 26

 

四、32位机和64位机内存对齐的区别

 

32位 64位
sizeof(char):1
sizeof(short):2
sizeof(int):4
sizeof(long):4
sizeof(long long):8
sizeof(unsigned int):4
sizeof(float):4
sizeof(double):8
sizeof(pointer):4
sizeof(char):1
sizeof(short):2
sizeof(int):4
sizeof(long):4
sizeof(long long):8
sizeof(unsigned int):4
sizeof(float):4
sizeof(double):8
sizeof(pointer):8

32位和64位系统在Windows下基本数据类型的大小都是一样的。只有指针的大小不一样!

32位指针大小为4byte,而64位的指针大小为8byte。

在内存对齐的规则上64位和32位机规则是一样的,只是有些数据结构和指针在64位和32位机所占大小不一样从而导致内存对齐后整体大小不一样

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章