【C++】記憶體對位
C++ 11 新引入的內容我幾乎在文章中已經全部介紹過了, 但是目前還剩下兩個沒有介紹, 一個是標識符 alignas
與運算子 alignof
, 另外一個是屬性. 屬性我們將在稍後介紹, 首先來介紹 alignas
與 alignof
在介紹這兩個關鍵字之前, 我需要引入記憶體對位的內容, 這並不是 C++ 11 才引入的
對於某一個固定的型別, 硬體是按照一定的倍數對記憶體進行存取的. 例如對於 64 位元的作業系統來說, 一個 int
型別的大小為 4. 因此, 硬體是按照 4 的倍數進行存取的. 若某一個變數的記憶體位址為 0x000000000004 (簡寫為 0x4), 則這一個 int
變數是對位的
給定一個記憶體位址和型別, 要測試是否對位, 我們可以使用
記憶體位址 % 型別大小
如若上述表達式回傳的是 0, 則這個型別是對位的; 如若上述表達式回傳的結果是 0 以外的任何數, 那麼記憶體就不是對位的
對於某個 int
變數, 假設這個變數的記憶體位址為 0x6, int
型別的大小為 4, 那麼硬體首先存取 0x4, 向後取 4 個大小的位元, 再存取 0x8, 向後取 4 個大小的位元, 最後將 0x6, 0x7, 0x8, 0x9 四個記憶體位址中對應的數據拼接之後再回傳. 如果記憶體是對位的, 那麼直接在起始位址向後存取型別大小個位元即可
記憶體對位與不對位相比較, 首先是效率問題. 不對位的記憶體要存取的次數比對位的記憶體要存取的次數多, 另外不對位的記憶體資料需要進行拼接. 然後是出錯的問題, 對於現代的民用電腦來說, 很少會存在這種問題, 但是對於有些 CPU 架構, 若存取的記憶體並不是對位的, 那麼硬體可能會擲出例外情況甚至拒絕存取的請求
C++ 編碼器在預設情況之下會自動對記憶體進行對位處理, 考慮下列結構體 (64 位元的作業系統下)
struct Foo {
char c[3];
int i;
};
在足夠理想的情況下, sizeof(Foo)
回傳的結果為 7
. 也就是說, 如果成員變數 c
被放置在 0x4 的記憶體位址處, 則成員變數 i
本應被放置在從 0x7 起的記憶體位址處
若編碼器沒有進行對位, 則記憶體中的數據是需要進行拼接的. 不但這個 Foo
型別的資料要進行拼接, 甚至位於它之後的所有資料都需要進行拼接. 如若在編碼器沒有進行對位的情況下, 那麼我們可以使用標識符 alignas
進行記憶體對位
alignas
的用法為 : alignas(expr)
、alignas(T)
或著 alignas(pack)
其中, T
為型別, 引數 expr
要求是一個結果大於等於 0 的常數表達式, pack
是一個包 (可以是型別也可以是常數表達式)
如果引數為一個型別, 那麼它首先要使用 alignof
運算子計算這個型別的記憶體對位. alignof
運算子和 sizeof
運算子比較類似, 在預設情況下, alignof
是佔用記憶體最大的那個成員變數的大小, 然後向 2 的乘方的結果對齊. 例如 Foo
類別中的成員變數 c
, 本身來說 alignof(c) == 3
, 但是編碼器對齊之後, 需要向 2 的乘方的結果對齊, 那麼 alignof(c) == 4
. 和 sizeof
相同, alignof
的引數如果是一個具體的變數, 那麼可以不用加括號. alignas(T)
相當於 alignas(alignof(T))
如果引數是一個常數表達式, 那麼要求這個引數必須是 0 或著 2 的乘方的結果, 否則會產生編碼錯誤. alignas(0)
通常會被編碼器忽略
alignas
可以被用於變數或著非位元欄位的成員變數的宣告中, 或著可以被用於類別 (struct
/ class
/ union
) 和列舉 (enum
) 的定義, 但是不可以被用於函式參數中或著 catch
捕獲列表中
除此之外, C++ 11 還規定, 當 alignas(c) <= alignas(T)
的時候, 對位要求會被編碼器忽略. 其中, c
為原始的對位值 :
alignas(2) int a;
int
型別對位本身需要 4 個大小, 但是實際對位的大小是 2, 它要小於本身需要的大小, 因此這個 alignas
的宣告就會被編碼器忽略. 在實際的編碼過程中, 這個宣告並不存在問題, 也就是不會產生編碼錯誤
我們使用 alignas
對型別 Foo
進行記憶體對位 :
struct alignas(4) Foo {
char c[3];
int i;
};
或著可以這樣 (int
是型別 Foo
的成員變數中佔用記憶體最大的型別) :
struct alignas(int) Foo {
char c[3];
int i;
}
還可以這樣 :
struct Foo {
alignas(4) char c[3];
int i;
}
此時, i
的偏移量為 c + 4
, 而不是 c + 3
. 編碼器會自動在 c + 3
處增加一個位元用於填充
我們之前已經說過, 編碼器會自動對所有的型別進行記憶體對位, 如果我們強行要求記憶體不對位或著使用我們自己的要求進行對位, 可以通過前置處理器指令
#pragma pack(n)
n
可以是任何前處理階段常數表達式 (你暫時可以理解為字面值), 當 n
< 0 的時候, 編碼器會發出警告. 如果我們希望記憶體一個一個對位, 那麼
#pragma pack(1)
此時, 如果再次宣告
struct Foo {
char c[3];
int i;
};
那麼 sizeof(Foo) == 7
在這樣的情況下, alignof
任何自訂型別的結果總是 1 (內建型別的 alignof
值不變)
自創文章, 原著 : Jonny. 如若閣下需要轉發, 在已經授權的情況下請註明本文出處 :