UBSan:未对齐地址的加载

dun*_*cks 5 c clang memcpy undefined-behavior ubsan

**编辑:问题不是关于未对齐数据访问的定义,而是为什么memcpy使ubsanitizers静音而类型转换没有,尽管生成相同的汇编代码**

我有一些示例代码来解析一个协议,该协议发送一个字节数组,该数组被分段为六个字节的组.

void f(u8 *ba) {
    // I know this array's length is a multiple of 6
    u8 *p = ba;
    u32 a = *(u32 *)p;
    printf("a = %d\n", a);
    p += 4;
    u16 b = *(u16 *)p;
    printf("b = %d\n", b);

    p += 2;
    a = *(u32 *)p;
    printf("a = %d\n", a);
    p += 4;
    b = *(u16 *)p;
    printf("b = %d\n", b);
}
Run Code Online (Sandbox Code Playgroud)

在将指针递增6并进行另一次32位读取后,UBSan会报告有关未对齐负载的错误.我使用memcpy而不是类型惩罚来压制这个错误,但我不太清楚为什么.要清楚,这是没有UBSan错误的相同例程,

void f(u8 *ba) {
    // I know this array's length is a multiple of 6 (
    u8 *p = ba;
    u32 a;
    memcpy(&a, p, 4);
    printf("a = %d\n", a);
    p += 4;
    memcpy(&b, p, 2);
    printf("b = %d\n", b);

    p += 2;
    memcpy(&a, p, 4);
    printf("a = %d\n", a);
    p += 4;
    memcpy(&b, p, 2);
    printf("b = %d\n", b);
}
Run Code Online (Sandbox Code Playgroud)

这两个例程都编译为相同的汇编代码(memcpy用于32位读取和movl16位读取),那么为什么一个未定义的行为,而另一个不是?是否movzwl有一些保证某些东西的特殊属性?

我不想在这里使用memcpy,因为我不能依赖编译器做一个足够好的工作来优化它.

Ant*_*ala 9

UB消毒剂用于信号的代码不严格符合要求和依赖,事实上,对不能保证未定义的行为.

实际上,C标准说这种行为是未定义的,一旦你施放一个指针,为地址被不宜对齐的类型.C11(草案,n1570)6.3.2.3p7:

指向对象类型的指针可以转换为指向不同对象类型的指针.如果对于引用的类型,结果指针未正确对齐(68),则行为未定义.

u8 *p = ba;
u32 *a = (u32 *)p; // undefined behaviour if misaligned. No dereference required
Run Code Online (Sandbox Code Playgroud)

存在这个铸造允许编译器来推定ba被对准以4字节边界(其中一个平台上u32,在这之后可以生成假定对准代码需要因此被对齐,这许多编译器将在x86做).

即使在x86平台上,也有令人失望的指令.即使看起来无辜的代码也可以编译成机器代码,这只会导致运行时中止.UBSan应该在代码中捕获它,否则当你运行它时看起来很健全,并且"按预期"运行,但如果使用另一组选项或不同的优化级别进行编译则会失败.

编译器可以为memcpy- 通常会生成完全相同的代码,但这仅仅是因为编译器将知道未对齐的访问在目标平台上工作并且运行良好.

最后:

我不想在memcpy这里使用,因为我不能依赖编译器做足够好的工作来优化它.

你在这里说的是:"我希望我的代码只有在垃圾或二十年前编译生成慢代码的编译器中才能可靠地运行.绝对不能用可以优化它来快速运行的编译器编译."

  • @yugr感谢您阅读我的回答.等一下.**当涉及到标准**时,无论平台如何,行为都是未定义的.当然,实现可以定义未定义的行为.你什么时候检查编译器的手册?!不,GCC,Clang,MSVC,这些都没有定义它. (2认同)