59

gcc-strict-aliasing-and-casting-through-a-union 中,我询问是否有人遇到过通过指针进行联合双关的问题。到目前为止,答案似乎是否定的。

这个问题更广泛:你有任何关于 gcc 和严格混叠的恐怖故事吗?

背景:引用AndreyT 在 c99-strict-aliasing-rules-in-c-gcc 中的回答

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

好吧,gcc现在敢于这样做了,它的-fstrict-aliasingswitch。这引起了一些问题。例如,参见关于 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 的回答中的第一个链接,确实符合恐怖故事的条件,可能有点过时(从 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 上使用gcc 4.3.4 和 -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;

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中是否可行,但到目前为止没有人不同意。

4

6 回答 6

34

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

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

日期 Wed, 26 Feb 2003 09:22:15 -0800 主题回复:没有 -fno-strict-aliasing 的无效编译来自 Jean Tourrilhes <>

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

Jean Tourrilhes <> 说:

对我来说它看起来像一个编译器错误......一些用户抱怨当以下代码在没有 -fno-strict-aliasing 的情况下编译时,write 和 memcpy 的顺序被反转(这意味着一个虚假的 len 是 mem-copyed进入溪流)。代码(来自 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 是一个宏,可以转换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 被 mem 复制到流中)。

“问题”是我们内联了 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,人们说我们可以做到这一点的标准”,完全没有自省地询问它是否有任何意义。

于 2010-06-02T16:09:47.697 回答
7

SWIG 生成依赖于关闭严格别名的代码,这可能会导致各种问题

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;
}
于 2011-09-22T17:19:04.293 回答
5

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)

我不知道这是否众所周知,我不知道这是一个错误还是一个功能。 我无法在 Cygwin 上复制 gcc 4.3.4的问题,因此它可能已被修复。一些解决方法:

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

进一步说明:

  • 这是一年零一天后对我自己的问题的答案(我有点惊讶只有两个其他答案)。
  • 我在我的实际代码上浪费了几个小时,一个卡尔曼滤波器。看似很小的变化会产生巨大的影响,可能是因为改变了 gcc 的自动内联(这是一个猜测;我仍然不确定)。但它可能不符合恐怖故事的条件。
  • 是的,我知道你不会这样写copy()。(顺便说一句,我有点惊讶地看到 gcc 没有展开双循环。)
  • 没有 gcc 警告开关,包括-Wstrict-aliasing=,在这里做了任何事情。
  • 一维可变长度数组似乎没问题。

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

我向GCC Bugzilla报告了它,但他们对旧的 4.1.2 不感兴趣,尽管(我相信)它是价值 10 亿美元的 RHEL5 的关键。它不会出现在 4.2.4 以上。

我有一个类似错误的稍微简单的示例,只有一个矩阵。编码:

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

于 2011-06-03T21:20:23.177 回答
3

here is mine:

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

it caused certain shapes in a CAD program to be drawn incorrectly. thank goodness for the project's leaders work on creating a regression test suite.

the bug only manifested itself on certain platforms, with older versions of GCC and older versions of certain libraries. and then only with -O2 turned on. -fno-strict-aliasing solved it.

于 2011-12-17T18:54:08.283 回答
2

C 的通用初始序列规则曾经被解释为可以编写一个函数,该函数可以处理各种结构类型的前导部分,只要它们以匹配类型的元素开头。在 C99 下,该规则已更改,因此它仅适用于所涉及的结构类型是同一联合的成员,其完整声明在使用点可见。

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

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

  2. 尽管 CIS 规则是根据联合来描述的,但它的主要用途在于它暗示了结构的布局和访问方式。如果 S1 和 S2 是共享 CIS 的结构,那么从外部源接受指向 S1 和 S2 的指针的函数将无法遵守 C89 的 CIS 规则,而不允许相同的行为对指向的指针有用实际上不在联合对象内的结构;鉴于已经为联合指定了 CIS 对结构的支持,因此指定对结构的支持将是多余的。

于 2016-08-19T22:29:27.430 回答
1

以下代码在 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;
}
于 2010-10-08T20:04:33.070 回答