gcc,严格别名和恐怖故事

问题描述 投票:46回答:6

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复制到流中)。

编译代码,在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这是否合适,但到目前为止没有人不同意。

c gcc strict-aliasing
6个回答
29
投票

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

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

Date Wed,26 Feb 2003 09:22:15 -0800 Subject Re:没有-fno-strict-aliasing的编译无效来自Jean Tourrilhes <>

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

Jean Tourrilhes <>说:

它看起来像是一个编译器错误...有些用户抱怨说,当编译下面的代码而没有-fno-strict-aliasing时,write和memcpy的顺序被反转(这意味着一个虚假的len被mem复制进入溪流)。代码(来自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来定义内核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/[email protected]/msg01647.html

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

...

我知道gcc会重新命令写入访问,这些访问显然是(静态地)相同的地址。 Gcc会突然想到这一点

unsigned long a;

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

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


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

5
投票

gcc,别名和2-D可变长度数组:以下示例代码复制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的组合导致了这个bug。


2
投票

以下代码在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;
}

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解决了它。


2
投票

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

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

  1. 如果必须通过union类型执行访问,则没有理由指定必须显示完整声明。
  2. 虽然CIS的规则是以工会的形式来描述的,但它的主要用处在于它对结构的布局和访问方式的暗示。如果S1和S2是共享CIS的结构,那么从外部源接受指向S1和S2的指针的函数就不可能符合C89的CIS规则,而不允许相同的行为对指针有用。实际上不在union对象中的结构;因此,指定CIS对结构的支持将是多余的,因为它已经为工会指定。
© www.soinside.com 2019 - 2024. All rights reserved.