gcc和windows對於modifier/attribute的支持實際上是差很少的。好比在gcc的例子中,內存對齊要寫成:windows
class X { //... } __attribute__((aligned(16)));
可是實際上你寫成數組
class __attribute__((aligned(16))) X { /*...*/ };
gcc同樣能夠識別。這樣MSVC和gcc就可使用宏完成跨平臺編譯。app
對齊在如下場合都能提示編譯器爲它的變量分配對齊的地址:函數
void foo() { X v; // v是個棧上的16字節對齊的變量 X* p = new X; // p是堆上的16字節對齊的指針 X* a = new X[ARRAY_SIZE]; // 那麼這個呢? }
棧上的變量堆上分配出的變量,由於align這個hint的存在,都能知足16字節對齊的要求。可是數組呢?按照通常規律來分析,對齊後的sizeof(X),必定是對齊的整數倍。好比16字節對齊的話,那麼X的大小隻能是16的倍數。因此對於本例的數組而言,編譯器應該也能知道a應該是16字節對齊的。spa
可是事實上挺奇怪。在MSVC上,p和a都很好的遵照了對齊的要求;在gcc上,p是對齊的,可是a卻不是。其實這個問題在2004年便有人提出來,只是到目前爲止一直都沒有人動手過。固然,標準也沒有規定X的數組就必定是要對齊的。要解決這個問題,要麼重載class的operator new/delete,要麼用memalign/aligned_malloc分配出對齊的內存,再placement new。出於易用性,我選擇的是操做符重載。線程
clang對於對齊的支持更乾脆:16B的對齊已經夠用了。因此align徹底被編譯器忽視了。結果Intel出來了AVX,Clang就傻逼了。不知道這個問題3.4會不會修正。指針
MSVC在x86下默認是支持的4B的內存對齊。也就是說在函數入口處,ESP和EBP只保證是4字節對齊的。這時,當前函數域棧上變量的地址都是ESP + 4 * x的形式。若是函數體內有對齊的變量,例如:code
void foo() { int __declspec(align(16)) x; // ... }
那麼編譯器在代碼生成時,會在函數的前部插入一段稱爲prolog的代碼,這段代碼會將堆棧修正爲16B對齊,好比blog
PUSH EBP MOV EBP, ESP SUB ESP, XXX AND ESP, 0xFFFFFFF0h
這樣ESP就必定是16字節對齊的。這個時候給x分配的地址,就能夠是ESP + 0x10 * n的形式,這樣就知足了對齊的須要。內存
在GCC上,gcc認爲全部的函數都有義務在調用其它函數的時候,ESP是16字節對齊的(固然,能夠經過編譯選項修改這一要求)。不光是調用方會這樣保證,被調用方也是這樣默認的。因此GCC爲了調用效率更高一點,便根據調用方的假設,去掉了「堆棧修正」這個步驟。
原來的代碼可能就變成了
PUSH EBP ; 假設這裏的ESP是16B對齊的,Push了EBP,ESP就是16x-4了。 MOV EBP, ESP SUB ESP, 0x0000023Ch ; 減完之後這裏又是16字節對齊了
那麼當被調用方遵照這個約定的時候,ESP固然就是16字節對齊的。可是有一種狀況例外。在MinGW下,線程的入口函數是被API回調的。這個函數極可能是按照Windows的標準4個字節對齊的。這樣,在沒有堆棧修正的狀況下,整個線程調用鏈16B對齊的默契就被打破了。若是這個時候出現了SSE代碼試圖存取「16字節對齊」的變量,那可能就會發生segment fault的異常,由於這些變量的地址並非對齊的。
解決這個問題,有兩種常見的辦法:第一,寫一個Wrapper函數,對齊ESP後轉發調用;第二,使用編譯選項-mstackrealign。這個選項會爲全部函數增長堆棧修正的PROLOG代碼,以保證函數棧幀必定是按照16字節或用戶指定大小對齊。