代码之家  ›  专栏  ›  技术社区  ›  Joseph Quinsey Taseen

gcc,严格的别名和恐怖故事

  •  44
  • Joseph Quinsey Taseen  · 技术社区  · 15 年前

    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)中,以及C++ 98(3.10/15)中。只是,并不是所有的编译器都希望(或敢于)实施或依赖它。”

    好, 合同通用条款 他现在敢于这样做 -fstrict-aliasing http://davmac.wordpress.com/2009/10/ 关于一个Mysql错误,以及 http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html .

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

    再说一遍,你有自己的恐怖故事吗?问题 表示为 -Wstrict-aliasing

    6月2日添加 :中的第一个链接 Michael Burr's answer 的确

    资料来源:

    #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;
    }
    

    具体投诉是:

    一些用户抱怨说,当编译[上述]代码时没有-fno严格的别名,写入和memcpy的顺序是颠倒的(这意味着一个伪len是mem复制到流中的)。

    合同通用条款 4.3.4关于CYGWIN wih-O3(如果我错了,请纠正我——我的汇编程序有点生锈了!):

    _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
    

    至于迈克尔回答的第二个环节,

    *(unsigned short *)&a = 4;
    

    合同通用条款 通常会(总是?)发出警告。但我 合同通用条款

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

    我已经问过了,这是否可以 ,但到目前为止没有人不同意。

    6 回复  |  直到 6 年前
        1
  •  33
  •   Peter Cordes    7 年前

    没有我自己的恐怖故事,但这里有一些引用莱纳斯托瓦尔兹(抱歉,如果这些已经在一个链接的参考问题):

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

    日期:2003年2月26日星期三09:22:15-0800 主题:没有-fno严格别名的无效编译 来自Jean Tourrilles<&燃气轮机;

    让·图里尔斯<&燃气轮机;说:

    在我看来,它就像一个编译器错误。。。 一些用户抱怨说,当下列代码 编译时没有-fno严格的别名,顺序写和 memcpy是反向的(这意味着一个伪len是mem复制到

    static inline 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;
    }
    

    重新订购是危险的。有什么建议让这个简单的代码更简单

    编译器可以自由地假设char*stream和struct iw\u event*iwe point

    这是真的,这不是我抱怨的问题。

    (事后诸葛亮:这段代码不错,但是Linux的 memcpy was a macro that cast to long * 大块地复制。具有正确定义的 内存 gcc -fstrict-aliasing 内存

    以及Linus Torvald对上述内容的评论:

    让·图里尔写道:

    在我看来,它就像一个编译器错误。。。

    为什么您认为内核使用“-fno strict aliasing”?

    海湾合作委员会的人更感兴趣的是试图找出什么是可能的 c99规范允许的,而不是实际制造东西 工作 . 这个 有可能理智地告诉gcc什么时候有些事情会发生。

    编译时没有-fno严格的别名,顺序写和 流)。

    “问题”是我们内联memcpy(),此时gcc不会 关心的事实是,它可以别名,所以他们只会重新订购 一切都是自己的错。即使没有理智

    不关心这个地区的真实世界。如果那样的话我会很惊讶的

    我不会费心去反抗的。

    莱纳斯

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

    基于类型的别名是 愚蠢的 . 太愚蠢了,一点都不好笑。它坏了。海湾合作委员会接受了这个破碎的概念,并使之更为如此,使之成为一个“按法律条文”的事情,没有任何意义。

    gcc将对显然是(静态地)同一地址的写访问进行重新排序。Gcc会突然想到

    unsigned long a;
    
    a = 5;
    *(unsigned short *)&a = 4;
    

    可能会被重新命令先将其设置为4(因为很明显他们没有别名-通过阅读标准),然后因为现在'a=5'的赋值是稍后的,所以4的赋值可以完全忽略!如果有人抱怨编译器疯了,编译器的人会说“nyaah,nyaah,标准的人说我们可以这样做”,完全没有反省地问它是否有意义。

        2
  •  7
  •   Community Mohan Dere    8 年前

    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;
    }
    
        3
  •  5
  •   Joseph Quinsey Taseen    14 年前

    以下示例代码复制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;
    }
    

    有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还是一个特性。 我不能重复gcc的问题 4.3.4 关于Cygwin ,所以可能已经修复了。一些解决方法:

    • 使用 __attribute__((noinline)) 对于copy()。
    • 使用gcc开关 -fno-strict-aliasing .
    • 将copy()的第三个参数从 b[][n] b[][2] .
    • 不要使用 -O2 -O3

    进一步说明:

    • 我在我的实际代码,一个卡尔曼滤波器上浪费了几个小时。看似很小的改变会产生剧烈的影响,也许是因为改变了gcc的自动内联(这只是猜测;我还不确定)。但它可能不符合 恐怖故事 .
    • 是的,我知道你不会写信 copy() 像这样(另外,我对gcc没有展开双循环感到有点惊讶。)
    • 无gcc警告开关,包括 -Wstrict-aliasing=
    • 一维变长数组似乎还可以。

    更新 :

    我向你报告了 GCC Bugzilla

    我有一个类似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

        4
  •  3
  •   don bright    10 年前

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

    它导致CAD程序中某些形状的绘制不正确。谢天谢地,项目负责人正在创建回归测试套件。

    这个bug只在某些平台上出现,包括GCC的旧版本和某些库的旧版本。然后只打开-O2-fno严格的别名解决了这个问题。

        5
  •  2
  •   supercat    9 年前

    C的一般初始顺序规则过去被解释为 各种各样的结构类型,只要它们从匹配的元素开始 类型。在C99下,规则被修改,所以它只适用于 所涉及的类型是同一工会的成员 完成

    合同通用条款的作者坚持认为,只有在下列情况下,有关的语言才适用

    1. 没有理由具体说明 完成 如果必须通过联合类型执行访问,则声明必须可见。

    2. 尽管独联体规则是以工会的形式来描述的,但它的主要 接受指向S1的指针的函数 来自外部来源的S2可以符合C89的CIS规则 实际上不在联合对象内部的结构;指定CI 因此,考虑到对结构的支持是多余的

        6
  •  1
  •   jamessan    15 年前

    下面的代码在GCC4.4.4下返回10。union方法或GCC4.4.4有什么问题吗?

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