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位機所佔大小不一樣從而導致內存對齊後整體大小不一樣

 

 

 

 

 

 

 

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