【问题标题】:C++: Alignment when casting byte buffer to another typeC ++:将字节缓冲区转换为另一种类型时的对齐
【发布时间】:2020-09-10 05:14:10
【问题描述】:

我有一块通过 char 缓冲区分配的内存,通过其他类型的缓冲区查看是否合法?

char* buffer = new char[1000];
int64_t* int64_view = static_cast<int64_t*>(static_cast<void*>(buffer))

int64_view[0] 是否保证对应buffer 的前 8 个字节? 我有点担心别名问题,如果char 缓冲区只有1 字节对齐,而int64_t 必须是8 字节对齐,那么编译器如何处理呢?

【问题讨论】:

    标签: c++ pointers


    【解决方案1】:

    您的示例违反了严格的别名规则。 所以,int64_view 无论如何都会指向第一个字节,但它可以是非对齐访问。有些平台允许,有些不允许。无论如何,在 C++ 中它是 UB。

    例如:

    #include <cstdint>
    #include <cstddef>
    #include <iostream>
    #include <iomanip>
    
    #define COUNT 8
    
    struct alignas(1) S
    {
        char _pad;
        char buf[COUNT * sizeof(int64_t)];
    };
    
    int main()
    {
        S s;
        int64_t* int64_view alignas(8) = static_cast<int64_t*>(static_cast<void*>(&s.buf));
    
        std::cout << std::hex << "s._pad     at " << (void*)(&s._pad) << " aligned as " << alignof(s._pad)     << std::endl;
        std::cout << std::hex << "s.buf      at " << (void*)(s.buf)   << " aligned as " << alignof(s.buf)      << std::endl;
        std::cout << std::hex << "int64_view at " << int64_view       << " aligned as " << alignof(int64_view) << std::endl;
    
        for(std::size_t i = 0; i < COUNT; ++i)
        {
            int64_view[i] = i;
        }
    
        for(std::size_t i = 0; i < COUNT; ++i)
        {
            std::cout << std::dec << std::setw(2) << i << std::hex << " " << int64_view + i << " : " << int64_view[i] << std::endl;
        }
    }
    
    

    现在用-fsanitize=undefined编译并运行它:

    $ g++ -fsanitize=undefined -Wall -Wextra -std=c++20 test.cpp -o test
    
    $ ./test
    s._pad     at 0x7ffffeb42300 aligned as 1
    s.buf      at 0x7ffffeb42301 aligned as 1
    int64_view at 0x7ffffeb42301 aligned as 8
    test.cpp:26:23: runtime error: store to misaligned address 0x7ffffeb42301 for type 'int64_t', which requires 8 byte alignment
    0x7ffffeb42301: note: pointer points here
     7f 00 00  bf 11 00 00 00 00 00 00  ff ff 00 00 01 00 00 00  20 23 b4 fe ff 7f 00 00  7c a4 9d 2b 98
                  ^ 
    test.cpp:31:113: runtime error: load of misaligned address 0x7ffffeb42301 for type 'int64_t', which requires 8 byte alignment
    0x7ffffeb42301: note: pointer points here
     7f 00 00  bf 00 00 00 00 00 00 00  00 01 00 00 00 00 00 00  00 02 00 00 00 00 00 00  00 03 00 00 00
                  ^ 
     0 0x7ffffeb42301 : 0
     1 0x7ffffeb42309 : 1
     2 0x7ffffeb42311 : 2
     3 0x7ffffeb42319 : 3
     4 0x7ffffeb42321 : 4
     5 0x7ffffeb42329 : 5
     6 0x7ffffeb42331 : 6
     7 0x7ffffeb42339 : 7
    

    它适用于 x86_64,但存在未定义的行为,您需要为执行速度付费。

    godbolt上的这个例子

    在 C++20 中有bit_cast。在此示例中,它对未对齐访问没有帮助,但它可以解决一些别名问题。

    更新: x86_64 上有说明,需要对齐访问。例如,需要 16 位对齐的 SSE。如果您尝试在未对齐的访问中使用这些指令,应用程序将因“一般保护错误”而崩溃。

    【讨论】:

    • 太棒了,非常感谢!只有一个后续:如果我碰巧确保 char 缓冲区与 8 的倍数对齐,那么应该没有问题,对吧(对于 int64_t)?
    • 不客气。)是的,在这个例子中。您可以通过从 S 结构中删除 _pad 并将对齐设置为 8 来测试它。消毒剂不会抱怨。在更复杂的代码中,编译器可能无法确定变量是否指向同一内存,如果您同时通过两个指针访问内存,它可以重新排序保存/加载指令。我会尝试做例子。我可以建议使用-Wstrict-aliasing(用于gcc和clang),它可以(!无法保证)警告您有关别名并且不要通过相邻行中不同类型的指针混合读/写到同一内存.
    【解决方案2】:

    void*肯定会导致UB。 static_cast 在您首先将您的类型转换为最通用的类​​型void* 时失去了它的价值,因为您可以将所有内容转换为/从void*。这与使用 reinterpret_cast 直接从您的类型转换为任何其他指针类型没有什么不同。

    考虑以下示例:

    int64_t* int64_view = reinterpret_cast<int64_t*>(buffer);
    

    它可能有效,也可能无效 - UB。

    【讨论】:

    • 谢谢,但是为什么它不起作用呢?是因为缺乏对齐保证吗?如果 int64_view 指向 char 缓冲区的开头,那么我看不到问题。如果它被某个值偏移以保证 8 对齐,那么它将不起作用。
    • 具体来说,alignof(buffer) 为 8,因此只要对齐为 8,这应该始终有效。是真的吗?
    • @PiotrDabkowski UB 表示未指定此类操作的行为,可以在未来的某一天定义。这意味着它可能会按照您认为它现在的工作方式进行定义,并且它可以做任何其他事情。强烈不建议使用这种类型的铸造。所以-根本没有保证,只要是UB。
    • 好吧,如果缓冲区 ptr 与 int64_t 对齐(alignof(buffer) 是 8 的倍数),则定义行为。事实证明,使用 new 时可以保证对齐:stackoverflow.com/questions/506518/…。我已经运行测试并且视图总是按预期工作(视图的第一个元素指向缓冲区的 8 个第一个元素)。我仍然不确定当缓冲区未对齐时 UB 会以什么方式表现出来?
    • “我仍然不确定当缓冲区未对齐时 UB 会以何种方式显示?”从技术上讲,没有人是。实际上,甚至可能没有任何表现形式。行为是undefined,必须按字面意思理解。从理论上讲,可能会发生任何类型的问题,这取决于编译器、架构等的突发奇想。标准根本不关心可能会发生什么,仅在声明你已经进入了一个他们不再保证将会发生什么的世界。
    猜你喜欢
    • 1970-01-01
    • 2020-12-02
    • 2023-04-06
    • 2016-09-23
    • 1970-01-01
    • 2011-09-24
    • 2018-06-25
    • 2020-12-14
    • 1970-01-01
    相关资源
    最近更新 更多