gcc、严格别名和恐怖故事[已关闭]

sh7euo9m  于 2023-08-03  发布在  其他
关注(0)|答案(6)|浏览(123)

已关闭。此问题需要更多focused。它目前不接受回答。
**希望改进此问题?**更新问题,使其仅针对editing this post的一个问题。

三年前就关了。
Improve this question
gcc-strict-aliasing-and-casting-through-a-union中,我问过是否有人遇到过通过指针进行联合双关的问题。到目前为止,答案似乎是“不”。
这个问题的范围更广:你有关于gcc和严格别名的恐怖故事吗?
背景:引自AndreyT在c99-strict-aliasing-rules-in-c-gcc中的回答:
“严格的别名规则源于自[标准化]时代开始以来就存在于C和C中的部分标准。C89/90(6.3)和C98(3.10/15)中都有禁止通过一个类型的左值访问另一个类型的对象的子句。......只是,并非所有的编译者都希望(或敢于)执行或依赖它。”
好吧,gcc现在已经大胆地这么做了,它的-fstrict-aliasing开关。而这也造成了一些问题。例如,请参阅关于Mysql bug的优秀文章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;
}

字符串
具体投诉内容为:
一些用户抱怨说,当编译[上述]代码时不使用-fno-strict-aliasing,write和memcpy的顺序会颠倒(这意味着一个伪造的len会被mem-copy到流中)。
编译代码,在CYGWIN wih -O3上使用gcc4.3.4(如果我错了,请纠正我--我的汇编程序有点生 rust 了!):

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


我曾经问过SO在gcc-strict-aliasing-and-casting-through-a-union中是否可以这样做,但到目前为止没有人不同意。

wi3ka0sx

wi3ka0sx1#

没有我自己的恐怖故事,但这里有一些引用Linus Torvalds(抱歉,如果这些已经在问题中的链接引用之一):
http://lkml.org/lkml/2003/2/26/158
日期2003年2月26日星期三09:22:15 -0800受试者回复:没有-fno-strict-aliasing的无效编译来自Jean Tourrilhes <>
On Wed,Feb 26,2003 at 04:38:10PM +0100,Horst von Brand wrote:
Jean Tourrilhes <>说道:
在我看来像是编译器的bug...一些用户抱怨说,当下面的代码在没有-fno-strict-aliasing的情况下编译时,write和memcpy的顺序被颠倒了(这意味着一个伪len被mem-copy到流中)。代码(来自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指向不同的内存区域。
这是真的,这不是我抱怨的问题。
(Note后见之明:这段代码很好,但是Linux的memcpy实现是一个宏,它强制转换为long *以进行更大的块复制。使用正确定义的memcpy,不允许gcc -fstrict-aliasing破坏此代码。但这意味着如果编译器不知道如何将字节复制循环转换为有效的asm,则需要内联asm或__attribute__((aligned(1),may_alias))e.g. in a typedef)来定义内核memcpy(gcc 7之前的gcc就是这种情况)
Linus Torvald的评论:
Jean Tourrilhes jt@bougret.hpl.hp.com写道:
在我看来像是编译器的bug...
你认为内核为什么使用“-fno-strict-aliasing”?
gcc的人更感兴趣的是试图找出c99规范允许什么,而不是让事情真正 * 工作 *。特别是别名代码甚至不值得启用,它只是不可能理智地告诉gcc什么时候有些东西可以别名。
一些用户抱怨说,当下面的代码在没有-fno-strict-aliasing的情况下编译时,write和memcpy的顺序被颠倒了(这意味着一个伪len被mem-copy到流中)。
“问题”是我们内联了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,标准的人说我们可以做到这一点”,绝对没有反省是否有任何意义。

rjee0c15

rjee0c152#

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

字符串

js81xvg6

js81xvg63#

**gcc、aliasing和二维变长数组:**以下示例代码复制了一个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上使用gcc4.1.2,我得到:

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


我不知道这是否是众所周知的,我不知道这是一个错误还是一个功能。* 我无法在Cygwin* 上复制gcc4.3.4的问题,因此它可能已被修复。一些变通方法:

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

进一步说明:

  • 这是一个答案,一年零一天后,我自己的问题(我有点惊讶,只有两个其他答案)。
  • 我花了几个小时在我的实际代码上,一个卡尔曼滤波器。看起来很小的改变会产生剧烈的影响,也许是因为改变了GCC的自动内联(这是一个猜测;我还不确定)。但它可能不符合“恐怖故事”的标准。
  • 是的,我知道你不会这样写copy()。(顺便说一句,我有点惊讶地看到gcc没有展开双循环。
  • 没有gcc警告开关,包括-Wstrict-aliasing=,在这里做任何事情。
  • 一维可变长度数组似乎没问题。
  • 更新**:* 上述内容并没有真正回答OP的问题,因为他(即I)问的是严格别名“合法地”破坏代码的情况,而上面的似乎只是一个普通的编译器错误。

我向GCC Bugzilla报告了这个问题,但他们对旧的4.1.2不感兴趣,尽管(我相信)它是价值10亿美元的RHEL 5的关键。它不会发生在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。

js4nwp54

js4nwp544#

以下是我的:
http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html
它导致CAD程序中的某些形状被错误地绘制。谢天谢地,项目的领导者们致力于创建一个回归测试套件。
这个错误只在某些平台上表现出来,包括旧版本的GCC和旧版本的某些库。然后只打开-O2。-fno-strict-aliasing解决了这个问题。

p4tfgftt

p4tfgftt5#

C语言的公共初始序列规则曾经被解释为可以编写一个可以在各种结构类型的前导部分工作的函数,只要它们以匹配类型的元素开始。在C99中,该规则被更改,因此它仅适用于所涉及的结构类型是同一个联合的成员,其 complete 声明在使用时可见。
gcc的作者坚持认为,所讨论的语言只适用于通过union类型执行访问的情况,尽管事实是:
1.如果必须通过联合类型执行访问,则没有理由指定 complete 声明必须可见。
1.尽管CIS规则是根据联合来描述的,但它的主要用途在于它对结构的布局和访问方式的暗示。如果S1和S2是共享一个CIS的结构,那么一个从外部源接受指向S1和S2的指针的函数就不可能遵守C89的CIS规则,而不允许相同的行为对指向实际上不在联合对象内部的结构的指针有用;因此,规定CIS对结构的支持是多余的,因为已经为工会规定了这一点。

iqxoj9l9

iqxoj9l96#

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

字符串

相关问题