2013-12-21 39 views
2

我正在處理一些需要大量內存的應用程序。爲了減少內存使用量,我將大對齊結構切換爲1字節(#pragma pack(1))。 之後,我的結構體積縮小了10-15%左右,但出現了一些問題。 當我嘗試通過指針或引用應用程序使用我的結構中的某個字段時,只會崩潰。如果我直接更改字段,它工作正常。C++ struct aligment爲1字節導致WinCE崩潰

在測試應用程序中,我發現問題在結構中使用小於4字節的字段後開始出現。

測試代碼:

#pragma pack(1) 
struct TestStruct 
{ 
    struct 
    { 
     long long lLongLong; 
     long lLong; 
     //bool lBool; // << if uncommented than crash 
     //short lShort; // << if uncommented than crash 
     //char lChar; // << if uncommented than crash 
     //unsigned char lUChar; // << if uncommented than crash 
     //byte lByte; // << if uncommented than crash 

     __int64 lInt64; 
     unsigned int Int; 
     unsigned int Int2; 
    } General; 
}; 

struct TestStruct1 
{ 
    TestStruct lT[5]; 
}; 
#pragma pack() 

void TestFunct(unsigned int &pNewLength) 
{ 
    std::cout << pNewLength << std::endl; 
    std::cout << "pNL pointer: " << &pNewLength << std::endl; 
    pNewLength = 7; // << crash 

    char *lPointer = (char *)&pNewLength; 
    *lPointer = 0x32; // << or crash here 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    std::cout << sizeof(TestStruct1) << std::endl; 

    TestStruct1 *lTest = new TestStruct1(); 
    TestFunct(lTest->lT[4].General.Int); 
    std::cout << lTest->lT[4].General.Int << std::endl; 

    char lChar; 
    std::cin >> lChar; 

    return 0; 
} 

編譯ARM的代碼(WinCE的6.0)導致崩潰。 Windows x86上的相同代碼可以正常工作。更換包裝(1)以包裝(4)或僅包裝()可解決此問題,但結構較大。

爲什麼這種對齊會導致問題?

+0

您是否試過訂購從大到小的會員?這是否有所作爲? –

+0

確實有幫助,但是在不同級別的嵌套結構上添加新的小字段會再次造成問題。 (1)很難管理多層次結構。 – sebeksd

回答

3

在x86上,未對齊的訪問速度很慢。 ARM扁平化不能做到。您的小類型會破壞下一個元素的對齊。

沒關係。如果按大小對成員進行排序,則開銷不可能超過3個字節。

+0

某些ARM處理器要求所有16位運算符都是半字對齊的,而32位操作數需要字對齊,但許多處理器可以容忍未對齊的16位和32位加載。處理器也有一些加載多寄存器指令,但是,那些不能容忍未對齊的訪問。我期望支持WinCE的平臺更可能使用支持非對齊訪問的ARM,但是當生成'int64'代碼時,編譯器可能會使用加載雙寄存器指令。 – supercat

+0

@supercat:考慮到它不起作用,我打算用這個特定的ARM確實需要對齊的假設。如果您正在編寫公共分母的代碼,則必須假定ARM不支持未對齊的訪問。 – MSalters

+0

我認爲ARM很可能支持正常加載指令的非對齊訪問,但編譯器在訪問結構時使用多字節加載指令。如果有人試圖在其中讀取或寫入int64,那麼情況尤其如此。 – supercat

4

您可以通過使用__unaligned關鍵字修復它(到與ARM WCE運行),我是能夠編譯此代碼與VS2005和成功WM5設備上運行,通過改變:

void TestFunct(unsigned int &pNewLength) 

void TestFunct(unsigned int __unaligned &pNewLength) 

使用此關鍵字將會使指令數超過兩倍,但可以使用任何傳統結構。

更多關於這個在這裏:

http://msdn.microsoft.com/en-us/library/aa448596.aspx

3

ARM架構只支持對齊內存訪問。這意味着四字節類型只能在4倍數的地址處讀寫。對於兩字節類型,地址必須是2的倍數。對未對齊內存訪問的任何嘗試通常都會給您帶來DATATYPE_MISALIGNMENT異常和隨後的崩潰。

現在,您可能會想知道爲什麼當通過未指定的結構成員傳遞指針和引用時纔開始看到崩潰;這與編譯器有關。只要您直接訪問結構中的字段,它就知道您正在訪問未對齊的數據,並通過以拆分和重組的幾個對齊塊來透明地讀取和寫入數據來處理它。我已經看到eVC++這樣做來編寫一個兩字節對齊的四字節結構成員:生成的程序集指令將整數分割爲單獨的兩字節並將它們分開寫入。

編譯器不知道指針或引用是否對齊,因此只要您傳遞未指定的結構字段作爲指針或引用,就沒有辦法讓它知道這些應該以特殊方式處理。它會將它們視爲對齊的數據並相應地訪問它們,這會導致地址未對齊時崩潰。

正如marcin_j所提到的,可以通過告訴編譯器一個特定的指針/引用與__unaligned關鍵字沒有對齊,或者說UNALIGNED宏,它在不需要它的平臺上什麼都不做。它基本上告訴編譯器以一種我認爲它與訪問未對齊結構成員的方式類似的方式小心指針。

一個簡單的方法是在整個代碼中抹灰UNALIGNED,但不建議這樣做,因爲它可能會導致性能損失:任何使用__unaligned的數據訪問都需要幾次內存讀/寫操作,而對齊版本只需要一次。 UNALIGNED通常僅用於代碼中已知未對齊的數據將被傳遞並遺漏在其他地方的地方。