[C/C++] 結構體內存對齊用法


一.為什么要內存對齊

  經過內存對齊之后,CPU的內存訪問速度大大提升;

  內存空間按照byte划分,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定變量的時候經常在特定的內存地址訪問,這就需要各類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。

  各個硬件平台對存儲空間的處理上有很大的不同。如果不按照適合其平台要求對數據存放進行對齊,會在存取效率上帶來損失。
比如有些平台每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那么一個讀周期就可以讀出,而如果存放在奇地址開始的地方,
就可能會需要2個讀周期,並對兩次讀出的結果的高低字節進行拼湊才能得到該int數據。顯然在讀取效率上下降很多。這也是空間和時間的取舍;
鏈接: https://www.cnblogs.com/jijiji/p/4854581.html
 

二. 內存對齊原則

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 類型名{
    成員表列
} 別名;
別名 變量;

基本數據類型所占內存大小

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM