首页 > 解决方案 > 是否有任何现代目标/编译器的示例在 C 中打破严格的别名会影响程序结果?

问题描述

有时有关堆栈溢出的问题和答案建议将指针强制转换为类型双关语的有效方式。它经常被声称这破坏了严格的别名并因此调用未定义的行为而被拒绝。

现代目标真的关心严格的混叠吗?在这种情况下,是否存在任何会显示意外行为的程序实例?

标签: cstrict-aliasing

解决方案


是的,严格别名是一种非常真实的现象,现代编译器经常利用它来执行优化。

考虑以下代码 -

typedef struct
{
    char a;
} my_struct;

void foo( int * a, my_struct * b, int count )
{
    int i;

    for (i=0;i<count;i++)
    {
        a[i] += b->a;
    }
}

当使用clang 3.8.0-2(这是一个非常现代的编译器)为 X64 目标(这也是一个非常现代的目标)编译时,使用以下命令 -

clang -m64 -S -O2 -std=c11 foo.c -fno-vectorize -fno-unroll-loops

生成以下程序集(简化并采用AT&T语法) -

foo:
    testl   %edx, %edx
    jle     .LBB0_3
    movsbl  (%rsi), %eax   # Value loaded only once before start of loop
    .align  16, 0x90
.LBB0_2:
    addl    %eax, (%rdi)
    addq    $4, %rdi
    decl    %edx
    jne     .LBB0_2
.LBB0_3:
    retq

可以看到 from 的值b->a在循环开始之前只加载一次,并添加到a.

但是如果这个函数被称为 -

my_struct a[100];
// initializaion of values in a;
...

foo((int*)a, a, 2);  // Breaking strict aliasing

现在很容易看出结果与预期不符。

在较低的优化级别-fno-strict-aliasing编译器在循环体中添加指令movsbl (%rsi), %eax

因此,可以公平地得出结论,具有现代编译器的现代架构确实利用了严格的别名,因此不能将指针强制转换用作类型双关语的一种方式。

参考:这个例子的灵感来自这篇博文


推荐阅读