一.為什么要內存對齊
經過內存對齊之后,CPU的內存訪問速度大大提升;
內存空間按照byte划分,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定變量的時候經常在特定的內存地址訪問,這就需要各類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
二. 內存對齊原則
1、數據成員對齊規則:結構(struct或聯合union)的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員存儲的起始位置: min(#pragma pack()指定的數,這個數據成員的自身長度)的倍數
2、結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從min(#pragram pack() , 內部長度最長的數據成員)的整數倍地址開始存儲。(struct a里存有struct b,b里有char,int,double等元素,那b應該從min(#pragram pack(), 8)的整數倍開始存儲。)
3、結構體的總大小,也就是sizeof的結果,必須是 min(#pragram pack() , 長度最長的數據成員) 的整數倍
#pragram pack() 默認為4(32位), 8(64位)
三. 用例
// #pragma pack(4) struct s1{ double b; char a; } s1; struct s2{ char a; double b; } s2; typedef struct s3{ char a; int b; double c; } s3; typedef struct s4{ char a; double c; int b; } s4; struct s5{ char a; s3 s; int b; } s5; struct s6{ char a; s4 s; int b; } s6;
加上 #pragma pack(4):
s1-s6 : 12, 12, 16, 16, 24, 24
不加 #pragma pack(4):(64位默認8):
s1-s6 : 16, 16, 16, 24, 32, 40
特別注意的是: c結構體中不允許定義static變量; C++結構體中可以定義static變量,sizeof時不計算該變量, 但需注意初始化格式,
C++ sizeof幾種變量的計算方法: https://blog.csdn.net/lime1991/article/details/44536343
四. 更改編譯器缺省字節對齊方式方法
法1. #pragma pack()用法
#pragma pack(n) /*指定按n字節對齊,等價於#pragma pack(push,n), n必須為2的n次方,否則編譯器可能會報錯*/ #pragma pack() /*取消指定對齊,恢復缺省對齊,等價於#pragma pack(pop)*/
鏈接:https://baike.baidu.com/item/%23pragma%20pack/3338249 參數介紹
法2.__attribute__((packed))用法
__attribute__((aligned(n))) // 讓所作用的數據成員對齊在n字節的自然邊界上;如果結構中有成員的長度大於n,則按照最大成員的長度來對齊; __attribute__((packed)) // 取消結構在編譯過程中的優化對齊,按照實際占用字節數進行對齊
注意: 兩個括號不能少
用例:
typedef struct s2{
char a;
int b;
double c;
} __attribute__((aligned(4))) s2;
typedef struct __attribute__((packed)) s2{
char a;
double c;
int b;
} s2;
五. 位域類型用法
注意,結構體內可以定義:位域類型; 位域類型沒怎么接觸過,有時間的話專門寫一篇進行下詳細了解;
參考鏈接: https://www.cnblogs.com/tsw123/p/5837273.html
struct A1{ char f1 : 3; char f2 : 4; char f3 : 5; }A1; struct A2{ char f1 : 3; char f2 ; char f3 : 5; }A2; struct A3{ char f1 : 3; short f2 ; char f3 : 5; }A3; struct A4{ char f1 : 3; int f2 : 5; char f3 : 5; }A4;
sizeof結果
A1-A4: 2, 3, 6, 4
六 常見的幾種誤用
#pragma pack(4) typedef struct s00{
char b;
int a; } s00; char c_temp[10] = "123456789"; s00 s_temp = { 0x09, 0x12345678};
此時, sizeof(s_temp) = 8, 內存中((char*)&s_temp)[0-7] = 0x09, 0x00,0x00,0x00,0x78,0x56,0x34,0x12 ;
1. memset(&s_temp, 0, sizeof(s_temp));
此時,memset會將8個字節都設為0, 在這里由於struct s_temp一般為malloc申請空間, malloc(sizeof(s00)), 默認申請為8個字節, 所以memset不會出錯;
若直接定義結構體s00 s_temp, 需考慮a后三個字節值可能不定,默認為0;
2. (s00*)c_temp;
若是將一個char*的數組強轉為s00, 該數組沒有考慮b后的三個空字符,那么強轉后參數就會出錯,
假設 c_temp[0-7] = 0x78,0x56,0x34,0x12, 0x09, 0x00,0x00,0x00
若s00,定義int在前,char在后; 此時a = 0x12345678, b = 0x09; // 正確,
若s00,定義char在前,int在后,此時b = 0x78, a = 0x09; // 錯誤,
3. memcpy(c_temp, (char *)&s_temp, sizeof(s_temp)); //最容易出錯
本意只想將s_temp的5個字節數據復制到c_temp, 但sizeof = 8, copy了8個字節, 會導致c_temp出錯;
此時c_temp[0-9] = 0x09, 0x00,0x00,0x00, 0x78,0x56,0x34,0x12, 0x39, 0x00; // 0x39 = '9'
其他
結構體定義方法
struct 類型名{ 成員表列 } 變量; struct 類型名 變量; typedef struct 類型名{ 成員表列 } 別名; 別名 變量;
基本數據類型所占內存大小

