gcc,严格别名和恐怖故事

时间:2010-06-02 14:27:01

标签: c gcc strict-aliasing

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)....只是并非所有编译器都希望(或敢于)强制执行或依赖它。“

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

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

重复一遍,你有自己的恐怖故事吗?当然,-Wstrict-aliasing指示的问题 是首选。还欢迎其他C编译器。

已添加6月2日 Michael Burr's answer中的第一个链接 确实 符合条件一个恐怖的故事,可能有点过时(从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被复制到流中)。 / p>

编译代码,在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 个答案:

答案 0 :(得分:28)

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

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

  

2003年2月26日星期三09:22:15 -0800   主题Re:没有-fno-strict-aliasing的编译无效   来自Jean Tourrilhes&lt;&gt;

     

2003年2月26日星期三04:38:10 PM +0100,Horst von Brand写道:

     
    

Jean Tourrilhes&lt;&gt;表示:

         
      

对我来说,它看起来像编译器错误...         有些用户抱怨说下面的代码是       在没有-fno-strict-aliasing的情况下编译,写入的顺序和       memcpy被反转(这意味着伪造的len被复制到了       流)。         代码(来自linux / include / net / iw_handler.h):

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_event * iwe指向     由于严格的别名,分离内存区域。

  
     

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

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

  

Linus Torvald对上述内容的评论:

     

Jean Tourrilhes写道:   &GT;

     
    

对我来说,它看起来像编译器错误......

  
     

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

     

gcc人们更有兴趣尝试找出可能的内容   允许使用c99规范,而不是实际上工作。该   特别是别名代码甚至不值得启用,它只是没有   当有些东西可以别名时,可以很好地告诉gcc。

     
    

有些用户抱怨说下面的代码是     在没有-fno-strict-aliasing的情况下编译,写入的顺序和     memcpy被反转(这意味着伪造的len被复制到了     流)。

  
     

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

     几年前我试图找到一个理智的方式,而gcc的开发人员确实如此   不关心这个领域的现实世界。如果那样的话,我会感到惊讶   从我已经看过的回复来看,已经改变了。

     

我不打算去打它。

     

莱纳斯

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

  

基于类型的别名是愚蠢的。这太令人难以置信的愚蠢,甚至都不好笑。它坏了。而gcc采取了破碎的观念,并通过使其成为“通过法律的信件”这一事实而更加明智。

     

...

     

我知道事实 gcc会重新排序明确(静态)相同地址的写访问。 Gcc突然想到了

unsigned long a;

a = 5;
*(unsigned short *)&a = 4;
     可以重新命令

将其设置为4(因为很明显它们不是别名 - 通过阅读标准),然后因为现在'a = 5'的赋值是后来的,4的赋值可能是完全省略了!如果有人抱怨编译器是疯了,编译人员会说“nyaah,nyaah,标准人们说我们可以做到这一点”,绝对没有反省询问是否有任何SENSE。

答案 1 :(得分:7)

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 :(得分:4)

gcc,别名和二维可变长度数组:以下示例代码复制了一个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的自动内联(这是猜测;我仍然不确定)。但它可能不符合恐怖故事
  • 是的,我知道你不会这样写copy()。 (而且,顺便说一下,我看到gcc没有展开双循环,我感到有些惊讶。)
  • 没有gcc警告开关,包括-Wstrict-aliasing=,在这里做了什么。
  • 1-D可变长度数组似乎没问题。

更新以上并没有真正回答OP的问题,因为他(即我)正在询问严格别名“合法地”破坏您的代码的情况,而以上似乎只是一个花园式的编译器错误。

我向GCC Bugzilla报告,但他们对旧的4.1.2不感兴趣,尽管(我相信)它是10亿美元RHEL5的关键。它不会出现在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会导致错误。

答案 3 :(得分:2)

以下代码在gcc 4.4.4下返回10。 union方法或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 :(得分:2)

这是我的:

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

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

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

答案 5 :(得分:2)

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

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

  1. 如果必须通过联合类型执行访问,则没有理由指定 complete 声明必须可见。

  2. 虽然CIS的规则是以工会的形式来描述的,但它是主要的 有用的在于它对结构形式的暗示 布局和访问。如果S1和S2是共享CIS的结构, 没有办法接受指向S1的指针 来自外部来源的S2可以符合C89的CIS规则 不允许相同的行为对指针有用 实际上不在union对象中的结构;指定CIS 因此,对结构的支持将是多余的 已经为工会指定。