目錄
一、內存對齊原因
我們知道內存的最小單元是一個字節,當cpu從內存中讀取數據的時候,是一個一個字節讀取,所以內存對我們應該是入下圖這樣:
但是實際上cpu將內存當成多個塊,每次從內存中讀取一個塊,這個塊的大小可能是2、4、8、16等。
那麼下面,我們來分析下非內存對齊和內存對齊的優缺點在哪?
內存對齊是操作系統爲了提高訪問內存的策略。操作系統在訪問內存的時候,每次讀取一定長度(這個長度是操作系統默認的對齊數,或者默認對齊數的整數倍)。如果沒有對齊,爲了訪問一個變量可能產生二次訪問。
- 至此大家應該能夠簡單明白,爲什麼要簡單內存對齊?
|
二、如何內存對齊
- 對於標準數據類型,它的地址只要是它的長度的整數倍。
- 對於非標準數據類型,比如結構體,要遵循一下對齊原則:
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。 |
手動設置對齊模數:
顯示當前packing alignment的字節數,以warning message的形式被顯示。
將當前指定的packing alignment數組進行壓棧操作,這裏的棧是the internal compiler stack,同事設置當前的packing alignment爲n;如果n沒有指定,則將當前的packing alignment數組壓棧。
從internal compiler stack中刪除最頂端的reaord; 如果沒有指定n,則當前棧頂record即爲新的packing alignement數值;如果指定了n,則n成爲新的packing alignment值
指定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 c 的開始地址是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 c 的開始地址是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位機所佔大小不一樣從而導致內存對齊後整體大小不一樣