背景:在任務中遇到了由於內存對齊引起的一個Double數據讀取錯誤問題,排查很久才發現偏移地址跑了4位。
內存對齊知識整理:
1、一個對齊的例子:
struct Struct1
{
char FileFlag[10]; //
char BuildUnit[100]; //
double BuildTime; // 偏移地址爲110 否則按照默認爲:112
};
以上結構體的前兩個數據單元爲char的數組,分別佔10個字節和100個字節的內存,但是,由於C++語言默認進行8字節對齊,buildTime這個佔8個字節的數據類型就會從8的最小整數倍作爲偏移地址:即偏移地址不是110,而是112。整個結構體的數據大小也不是118,而是120字節。
2、內存對齊的好處:
簡單來說,是爲了提高處理器對於數據的處理效率。
這裏涉及一個概念:cpu的內存訪問粒度。目前的訪問粒度都是4字節,也就是說cpu讀取數據的起始偏移地址永遠是4的整數倍。而我們常見的int型、double、long等佔字節數較多的數據類型都是4的整數倍的字節佔用,因此,如果這類數據的起始地址是4的整數倍,cpu就可以以最小的代價獲取到數據,不用多進行一次數據訪問然後再進行兩次查詢結果的拼接。
因此,c++語言在執行編譯的時候自動對數據結構裏的數據執行了對齊操作,加快運行效率。
具體的,
數據類型對齊:(由於cpu訪問粒度爲4,大於4個字節的數據類型的對齊即爲不小於自身大小的4的整數倍)
類型 |
對齊值(字節) |
char |
1 |
short |
2 |
int |
4 |
float |
4 |
double |
8 |
類數據對齊:
使用成員當中最大的對齊字節來對齊。比如在Struct A中,int a的對齊字節爲4,比char,short都大,所以A的對齊字節爲4
指定對齊字節:
宏 #pragma pack(n)來指定的對齊值(後續有詳細使用方法)
有效對齊字節值:
有效對齊字節=MIN(自身對齊字節, 指定對齊字節值)
可以隨時使用sizeof運算符來計算此時的結構體大小來確認對齊情況。
3、如何調整對齊規則:
有時候,面對傳輸過來的數據,我們需要建立結構體來解析數據,如果數據本身沒有按照我們這邊的對齊方式來仿製數據就會讀取錯誤。
方法爲:
#progma pack(n) // n = 1, 2, 4, 8
這個設置是全局的,如果只是修改某個結構的對齊方式,需要加入宏堆棧概念:
#progma pack(push)
#progma pack(1)
...
#progma pack(pop)
舉例:
#pragma pack(push)
#pragma pack(1)
struct TFileHeadInfo
{
char FileFlag[10]; //
char BuildUnit[100]; //
double BuildTime; // 偏移地址爲110 否則按照默認爲:112
};
#pragma pack(pop)
也可以寫作:
#pragma pack(push, 1)
...
#pragma pack(pop)
其他:
- 內存對齊對於32位和64位平臺有些許不同(32位cpu一次最多能處理32bits的信息,64位最多64bits)
- 使用sizeof運算符可以得到struct和union的確切大小(其中包含了因內存對齊所產生的內存碎片)
- 可以使用offsetof宏來得到一個數據成員在結構體中的位置
- 編譯器會對內存的分佈進行優化,所以理論計算值和實際輸出值有時會有些許不同
- wnidows的默認對齊數爲8,Linux的默認對齊數是4(可以理解爲pragma宏的默認設置值)
- 結構體中數據成員的順序可能會影響整個結構體所佔內存的大小