我正在开发一些需要大量内存的应用程序。为了减少内存使用量,我已将巨大结构的对齐切换为1个字节(#pragma pack(1))。 在此之后,我的结构尺寸减小了约10-15%,但出现了一些问题。 当我尝试通过指针或引用应用程序使用我的结构中的一个字段时崩溃。如果我直接改变字段就可以了。
在测试应用程序中,我发现在struct中使用小于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上的相同代码正常工作。将pack(1)更改为pack(4)或仅将pack()更改为解决此问题,但结构更大。
为什么这种对齐会导致问题?
答案 0 :(得分:4)
您可以使用__unaligned关键字修复它(使用ARM在WCE上运行),我能够使用VS2005编译此代码并成功在WM5设备上运行,方法是更改:
void TestFunct(unsigned int &pNewLength)
到
void TestFunct(unsigned int __unaligned &pNewLength)
使用此关键字将使指令数量增加一倍以上,但允许使用任何传统结构。
更多关于此:
答案 1 :(得分:3)
在x86上,未对齐的访问速度很慢。 ARM扁平化不能做到这一点。您的小类型会破坏下一个元素的对齐方式。
不重要。如果按大小对成员进行排序,开销不可能超过3个字节。
答案 2 :(得分:3)
ARM体系结构仅支持对齐的内存访问。这意味着四字节类型只能在4的倍数地址上读写。对于双字节类型,地址必须是2的倍数。任何未对齐内存访问的尝试通常都会通过DATATYPE_MISALIGNMENT异常和随后的崩溃来奖励您。
现在你可能想知道为什么你只是在将未对齐的结构成员作为指针和引用传递时才开始看到崩溃;这与编译器有关。只要您直接访问结构中的字段,它就会知道您正在访问未对齐的数据并通过透明地读取和写入分割和重新组合的多个对齐块中的数据来处理它。我已经看到eVC ++这样做是为了编写一个两字节对齐的四字节结构成员:生成的汇编指令将整数分成单独的两字节块并分别写入。
编译器不知道指针或引用是否对齐,因此只要将未对齐的结构字段作为指针或引用传递,就无法知道这些应该以特殊方式处理。它会将它们视为对齐的数据并相应地访问它们,这会在地址未对齐时导致崩溃。
正如marcin_j所提到的那样,通过告诉编译器特定的指针/引用与__unaligned关键字不对齐,或者更确切地说UNALIGNED宏在不需要它的平台上什么都不做,可以解决这个问题。它基本上告诉编译器要小心指针,我认为它与未对齐结构成员的访问方式类似。
一种天真的方法是在整个代码中使用UNALIGNED,但不推荐使用,因为它会导致性能损失:使用__unaligned的任何数据访问都需要几次内存读/写,而对齐版只需要一次。 UNALIGNED通常仅用于代码中的地方,在这些地方,未对齐的数据将被传递,并在其他地方被遗漏。