【问题标题】:C++ struct aligment to 1 byte causes crash on WinCEC++ 结构对齐到 1 个字节导致 WinCE 崩溃
【发布时间】:2013-12-21 21:27:37
【问题描述】:

我正在开发一些需要大量内存的应用程序。为了减少内存使用,我已将大型结构的对齐方式切换为 1 字节(#pragma pack(1))。 在此之后,我的结构大小小了大约 10-15%,但出现了一些问题。 当我尝试通过指针或引用应用程序使用我的结构中的一个字段时,它就会崩溃。如果我直接更改字段,它可以正常工作。

在测试应用程序中,我发现在结构中使用小于 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() 可以解决此问题,但结构更大。

为什么这种对齐会导致问题?

【问题讨论】:

  • 您是否尝试过将成员从大到小排序?这有什么不同吗?
  • 确实有帮助,但是在嵌套结构的不同级别添加新的小字段又会导致问题。使用 pack(1) 很难管理多级结构。

标签: c++ visual-studio-2008 struct windows-ce memory-alignment


【解决方案1】:

您可以使用 __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

【讨论】:

    【解决方案2】:

    ARM 架构仅支持对齐的内存访问。这意味着 4 字节类型只能在 4 的倍数的地址上读写。对于 2 字节类型,地址必须是 2 的倍数。任何未对齐内存访问的尝试通常都会以 DATATYPE_MISALIGNMENT 异常和随后的崩溃来奖励您。

    现在您可能想知道为什么只在将未对齐的结构成员作为指针和引用传递时才开始看到崩溃;这与编译器有关。只要您直接访问结构中的字段,它就知道您正在访问未对齐的数据并通过透明地读取和写入多个对齐的块中的数据来处理它,这些块被拆分和重新组合。我见过 eVC++ 这样做是为了编写一个双字节对齐的四字节结构成员:生成的汇编指令将整数分成单独的两字节片段并分别写入。

    编译器不知道指针或引用是否对齐,因此一旦您将未对齐的结构字段作为指针或引用传递,它就无法知道这些应该以特殊方式处理。它会将它们视为对齐的数据并相应地访问它们,这会在地址未对齐时导致崩溃。

    正如 marcin_j 所提到的,可以通过告诉编译器特定的指针/引用与 __unaligned 关键字不对齐,或者更确切地说 UNALIGNED 宏在不需要它的平台上不执行任何操作来解决此问题。它基本上告诉编译器要小心指针,我认为它类似于访问未对齐的结构成员的方式。

    一种天真的方法是将 UNALIGNED 涂满整个代码,但不建议这样做,因为它会导致性能损失:使用 __unaligned 的任何数据访问都需要多次内存读/写,而对齐版本只需要一次。 UNALIGNED 通常只用在代码中已知会传递未对齐数据的地方,而在其他地方被忽略。

    【讨论】:

      【解决方案3】:

      在 x86 上,未对齐的访问速度很慢。 ARM 完全做不到。您的小类型破坏了下一个元素的对齐方式。

      这并不重要。如果您按大小对成员进行排序,开销不太可能超过 3 个字节。

      【讨论】:

      • 一些 ARM 处理器要求所有 16 位操作符都是半字对齐的,而 32 位操作数是字对齐的,但许多处理器可以容忍未对齐的 16 位和 32 位加载。然而,处理器也有一些加载多寄存器指令,这些指令不会容忍未对齐的访问。我希望支持 WinCE 的平台更可能使用支持非对齐访问的 ARM,但是在生成 int64 代码时,编译器可能使用的是加载两个寄存器指令。
      • @supercat:考虑到它不起作用,我将假设这个特定的 ARM 确实需要对齐。如果您要为共同点编写代码,则必须假设 ARM 不支持非对齐访问。
      • 我认为 ARM 更有可能支持正常加载指令的非对齐访问,但编译器在访问结构时使用多字节加载指令。如果尝试在其中读取或写入 int64,则这种情况尤其可能发生。
      猜你喜欢
      • 1970-01-01
      • 2019-07-14
      • 2016-06-25
      • 2011-09-23
      • 1970-01-01
      • 2021-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多