首页 文章

gcc,严格别名和恐怖故事

提问于
浏览
44

gcc-strict-aliasing-and-casting-through-a-union中,我问是否有人通过指针遇到工会打击问题 . 到目前为止,答案似乎是否定的 .

这个问题更广泛:你有关于gcc和严格别名的恐怖故事吗?

背景:引自AndreyT's answer in c99-strict-aliasing-rules-in-c-gcc

“严格的别名规则植根于自[标准化]时代开始以来C和C中存在的标准部分 . 禁止通过另一种类型的左值访问一种类型的对象的条款存在于C89 / 90中( 6.3)以及C98(3.10 / 15)......只是并非所有编译器都希望(或敢于)强制执行或依赖它 .

好吧, gcc 现在敢于这样做,其 -fstrict-aliasing 开关 . 这引起了一些问题 . 例如,请参阅关于Mysql错误的优秀文章http://davmac.wordpress.com/2009/10/,以及http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html中同样出色的讨论 .

其他一些不太相关的链接:

重复一遍,你有自己的恐怖故事吗? -Wstrict-aliasing 表示的问题 not 当然是首选 . 其他C编译器也很受欢迎 .

Added June 2nd :迈克尔伯尔的答案中的第一个链接,其确定 indeed 有资格作为一个恐怖故事,可能有点过时(从2003年开始) . 我做了一个快速测试,但问题显然已经消失了 .

资源:

#include <string.h>
struct iw_event {               /* dummy! */
    int len;
};
char *iwe_stream_add_event(
    char *stream,               /* Stream of events */
    char *ends,                 /* End of stream */
    struct iw_event *iwe,       /* Payload */
    int event_len)              /* Real size of payload */
{
    /* Check if it's possible */
    if ((stream + event_len) < ends) {
            iwe->len = event_len;
            memcpy(stream, (char *) iwe, event_len);
            stream += event_len;
    }
    return stream;
}

具体投诉是:

有些用户抱怨说,当编译[above]代码而没有-fno-strict-aliasing时,write和memcpy的顺序会被反转(这意味着虚假len被mem复制到流中) .

编译代码,在CYGWIN wih -O3上使用 gcc 4.3.4(如果我错了请纠正我 - 我的汇编程序有点生锈!):

_iwe_stream_add_event:
        pushl       %ebp
        movl        %esp, %ebp
        pushl       %ebx
        subl        $20, %esp
        movl        8(%ebp), %eax       # stream    --> %eax
        movl        20(%ebp), %edx      # event_len --> %edx
        leal        (%eax,%edx), %ebx   # sum       --> %ebx
        cmpl        12(%ebp), %ebx      # compare sum with ends
        jae L2
        movl        16(%ebp), %ecx      # iwe       --> %ecx
        movl        %edx, (%ecx)        # event_len --> iwe->len (!!)
        movl        %edx, 8(%esp)       # event_len --> stack
        movl        %ecx, 4(%esp)       # iwe       --> stack
        movl        %eax, (%esp)        # stream    --> stack
        call        _memcpy
        movl        %ebx, %eax          # sum       --> retval
L2:
        addl        $20, %esp
        popl        %ebx
        leave
        ret

而对于Michael的回答中的第二个链接,

*(unsigned short *)&a = 4;

gcc 通常(总是?)发出警告 . 但我相信对此有效的解决方案(对于 gcc )是使用:

#define CAST(type, x) (((union {typeof(x) src; type dst;}*)&(x))->dst)
// ...
CAST(unsigned short, a) = 4;

我已经问过gcc-strict-aliasing-and-casting-through-a-union这是否合适,但到目前为止没有人不同意 .

6 回答

  • 7

    没有我自己的恐怖故事,但这里有来自Linus Torvalds的一些引用(对不起,如果这些已经在问题中的一个链接引用中):

    http://lkml.org/lkml/2003/2/26/158

    2003年2月26日星期三09:22:15 -0800主题回复:没有-fno-strict-aliasing的编译无效来自Jean Tourrilhes <> 2003年2月26日星期三下午04:38:10 0100,Horst von Brand写道:Jean Tourrilhes <>说:它看起来像是一个编译器错误...有些用户抱怨说,当编译下面的代码而没有-fno-strict-aliasing时,write和memcpy的顺序被反转(这意味着将伪造的len复制到流中 . 代码(来自linux / include / net / iw_handler.h):静态内联字符*
    iwe_stream_add_event(char * stream,/ 事件流 /
    char *结束,/ 流结束 /
    struct iw_event * iwe,/ * Payload * /
    int event_len)/ 有效载荷的实际大小 /
    {
    / 检查是否可能 /
    if((stream event_len)<ends){
    iwe-> len = event_len;
    memcpy(stream,(char *)iwe,event_len);
    stream = event_len;
    }
    回流;
    }
    恕我直言,编译器应该有足够的上下文来知道重新排序是危险的 . 任何建议使这个简单的代码更加防弹是受欢迎的 . 由于严格的别名,编译器可以自由地假设char * stream和struct iw_event * iwe指向单独的内存区域 . 哪个是真的,哪个不是我抱怨的问题 .

    (事后注意:这段代码很好,但是Linux的 memcpy was a macro that cast to long *实现了更大的块 . 使用正确定义的 memcpygcc -fstrict-aliasing 不允许破坏这段代码 . 但这意味着你需要内联asm来定义内核 memcpy 如果您的编译器不知道如何将字节复制循环转换为高效的asm,gcc7之前的gcc就是这种情况)

    和Linus Torvald对上述内容的评论:Jean Tourrilhes写道:>它看起来像是一个编译器错误...为什么你认为内核使用“-fno-strict-aliasing”? gcc人更感兴趣的是试图找出c99规范允许的内容,而不是让事情真正起作用 . 特别是别名代码甚至不值得启用,当某些东西可以别名时,不可能巧妙地告诉gcc . 一些用户抱怨说,当编译下面的代码而没有-fno-strict-aliasing时,write和memcpy的顺序会被反转(这意味着一个伪造的len被复制到流中) . 问题是我们内联memcpy(),此时gcc不关心它可以别名的事实,所以他们只是重新排序所有内容并声称它是自己的错 . 即使我们甚至没有理智告诉gcc这件事 . 几年前我试图找到一个理智的方式,而gcc开发人员真的不关心这个领域的现实世界 . 如果情况有所改变,我会感到惊讶,从我已经看过的回复来看 . 我不打算去打它 . 莱纳斯

    http://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg01647.html

    基于类型的别名是愚蠢的 . 这太令人难以置信的愚蠢,甚至都不好笑 . 它坏了 . 而gcc采取了破碎的观念,并通过使其成为“通过法律的信件”这一点毫无意义而更加如此 . ...我知道gcc会重新命令写入访问,这些访问显然是(静态地)相同的地址 . Gcc会突然觉得unsigned long;

    a = 5;
    (无符号短)&a = 4;
    可以重新命令将它设置为4(因为很明显它们没有别名 - 通过阅读标准),然后因为现在'a = 5'的分配是后来的,4的分配可以完全省略!如果有人抱怨编译器是疯了,编译人员会说“nyaah,nyaah,标准人们说我们可以做到这一点”,绝对没有反省询问是否有任何SENSE .

  • 2

    SWIG生成的代码依赖于严格的别名关闭,这可能会导致all sorts of problems .

    SWIGEXPORT jlong JNICALL Java_com_mylibJNI_make_1mystruct_1_1SWIG_12(
           JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2) {
      jlong jresult = 0 ;
      int arg1 ;
      int arg2 ;
      my_struct_t *result = 0 ;
    
      (void)jenv;
      (void)jcls;
      arg1 = (int)jarg1; 
      arg2 = (int)jarg2; 
      result = (my_struct_t *)make_my_struct(arg1,arg2);
      *(my_struct_t **)&jresult = result;              /* <<<< horror*/
      return jresult;
    }
    
  • 2

    gcc, aliasing, and 2-D variable-length arrays: 以下示例代码复制2x2矩阵:

    #include <stdio.h>
    
    static void copy(int n, int a[][n], int b[][n]) {
       int i, j;
       for (i = 0; i < 2; i++)    // 'n' not used in this example
          for (j = 0; j < 2; j++) // 'n' hard-coded to 2 for simplicity
             b[i][j] = a[i][j];
    }
    
    int main(int argc, char *argv[]) {
       int a[2][2] = {{1, 2},{3, 4}};
       int b[2][2];
       copy(2, a, b);    
       printf("%d %d %d %d\n", b[0][0], b[0][1], b[1][0], b[1][1]);
       return 0;
    }
    

    使用CentOS上的gcc 4.1.2 ,我得到:

    $ gcc -O1 test.c && a.out
    1 2 3 4
    $ gcc -O2 test.c && a.out
    10235717 -1075970308 -1075970456 11452404 (random)
    

    我不知道这是一个bug还是一个功能 . 我不能在Cygwin上用gcc 4.3.4 复制问题,所以它可能已被修复 . 一些解决方法:

    • 使用 __attribute__((noinline)) 进行复制() .

    • 使用gcc开关 -fno-strict-aliasing .

    • 将copy()的第三个参数从 b[][n] 更改为 b[][2] .

    • 不要使用 -O2-O3 .

    附加说明:

    • 这是一年又一天之后的回答,我自己的问题(我有点惊讶,只有两个答案) .

    • 我的实际代码卡尔曼滤波器丢失了几个小时 . 看似微不足道的变化会产生巨大的影响,也许是因为改变gcc 's automatic inlining (this is a guess; I'仍然不确定) . 但它可能不符合恐怖故事的条件 .

    • 是的,我知道你不会这样写 copy() . (而且,作为一个旁边,我有点惊讶地看到gcc没有展开双循环 . )

    • 没有gcc警告开关,包括 -Wstrict-aliasing= ,在这里做了什么 .

    • 1-D可变长度数组似乎没问题 .

    Update :以上并没有真正回答OP 's question, since he (i.e. I) was asking about cases where strict aliasing '合法地'破坏了你的代码,而上述似乎只是一个花园式的编译器bug .

    我把它报告给了GCC Bugzilla,但它们并没有发生在4.2.4以上 .

    我有一个稍微简单的类似bug的例子,只有一个矩阵 . 代码:

    static void zero(int n, int a[][n]) {
       int i, j;
       for (i = 0; i < n; i++)
       for (j = 0; j < n; j++)
          a[i][j] = 0;
    }
    
    int main(void) {
       int a[2][2] = {{1, 2},{3, 4}};
       zero(2, a);    
       printf("%d\n", a[1][1]);
       return 0;
    }
    

    产生结果:

    gcc -O1 test.c && a.out
    0
    gcc -O1 -fstrict-aliasing test.c && a.out
    4
    

    它似乎是组合 -fstrict-aliasing-finline 导致错误 .

  • 25

    以下代码在gcc 4.4.4下返回10 . 联合方法或gcc 4.4.4有什么问题吗?

    int main()
    {
      int v = 10;
    
      union vv {
        int v;
        short q;
      } *s = (union vv *)&v;
    
      s->v = 1;
    
      return v;
    }
    
  • 4

    这是我的:

    http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html

    它导致CAD程序中的某些形状被错误地绘制 . 谢天谢地,项目负责人致力于创建回归测试套件 .

    该错误仅在某些平台上表现出来,旧版本的GCC和某些库的旧版本 . 然后只有-O2打开 . -fno-strict-aliasing解决了它 .

  • 2

    C的公共初始序列规则曾经被解释为可以编写一个可以在各种结构类型的前导部分工作的函数,前提是它们以匹配类型的元素开始 . 在C99下,规则被更改,以便仅在涉及的结构类型是同一联盟的成员时才应用,其完整声明在使用点可见 .

    gcc的作者坚持认为,只有通过联合类型执行访问时,所讨论的语言才适用,尽管有以下事实:

    • 如果必须通过union类型执行访问,则没有理由指定必须显示完整声明 .

    • 虽然CIS的规则是以工会的形式来描述的,但它的主要用处在于它对结构的布局和访问方式的暗示 . 如果S1和S2是共享CIS的结构,那么就会存在从外部源接受指向S1和S2的指针的函数不能遵守C89的CIS规则,而不允许相同的行为对指向实际不在union对象内的结构的指针有用 . 因此,指定CIS对结构的支持将是多余的,因为它已经为工会指定 .

相关问题