当前位置:  开发笔记 > 编程语言 > 正文

gcc,严格别名和恐怖故事

如何解决《gcc,严格别名和恐怖故事》经验,为你挑选了3个好方法。

在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-aliasing开关来做到这一点.这引起了一些问题.例如,请参阅有关Mysql错误的优秀文章 http://davmac.wordpress.com/2009/10/,以及http://cellperformance.beyond3d.com/articles/2006/06/understanding中同样出色的讨论.-strict-aliasing.html.

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

业绩冲击的-FNO严格走样

严格走样

当-是炭安全换严格指针走样

如何对检测严格走样,在编译时

重复一遍,你有自己的恐怖故事吗?当然,没有表示的问题-Wstrict-aliasing是优选的.其他C编译器也很受欢迎.

6月2日补充:迈克尔伯尔的答案中的第一个链接,确实有资格作为恐怖故事,可能有点过时(从2003年开始).我做了一个快速测试,但问题显然已经消失了.

资源:

#include 
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中这是否正常,但到目前为止没有人不同意.



1> Michael Burr..:

没有我自己的恐怖故事,但这里有来自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 是一个宏,可以long *在更大的块中进行复制.使用正确定义的memcpy,gcc -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/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.


国际海事组织,莱纳斯似乎是这里愚蠢的人.
@Alok Linus是现货.标准严格别名规则的*intent*允许编译器在**不**的情况下进行优化,**不能**知道对象是否有别名.规则允许优化器在这些情况下不做出最坏情况下的混叠假设.但是,当从上下文*中清楚*对象*是别名*时,编译器应该将该对象视为别名,无论使用何种类型来访问它.否则不符合语言别名规则的意图.
因此,标准的措辞*允许*悲观上下文不敏感的实现故意打破明显的类型惩罚(违反法律的"精神").标准允许邪恶,它应该有一天被修复,但它仍然没有*要求*它:邪恶仍然是一个有意识的选择.完全有可能编写一个符合常见惯用语并且相应行为的符合要求的实现,但语言律师却极力违反最少意外的原则并编写法律允许的最残酷的实现.Linus的"nyaah,nyaah"特征是现货.
@Alok看到第二个链接,编译器确实对`-fstrict-aliasing`有点疯狂
这是GCC的错误和标准的错误.Dan Molding对于严格别名规则的意图是正确的.标准的问题在于它没有明确地命名这个意图并且防止恶意的"Jackass Genie"实现.它应该说明一下"编译器必须至少执行XYZ以确定两个对象是否相互别名.如果它们被证明是别名,则必须将它们视为别名.如果证明它们不是别名,则编译器可以自由地进行相应的优化.如果编译器无法证明,基于类型的别名规则也适用."
@MikeS绝对错了.没有这样的法律精神.精神是:*遵守规则,该死!*
@curiousguy:C语言规范不足以允许编写不依赖于标准未指定的内容的实际非平凡程序.此外,许多C编译器历来记录了他们在C标准"未定义"的情况下的行为.恕我直言,应该努力允许程序员指定他们与平台相关的假设,以便允许编译器在可能的情况下表现得恰当,或者如果他们不能,则拒绝编译.
@everyone:2011年,LLVM项目的Chris Lattner撰写了一系列博客文章,解释了未定义的行为如何影响编译器的运行方式.更重要的是,他谈到了一些关于UB的规则如何影响优化.特别是在第一部分http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html中,他简要讨论了类型惩罚.整篇文章系列非常值得一读.它至少给出了类型双关语是UB的一些基本原理.您是否认为价值证明决定使其未定义的行为仍然由您来决定.
@DanMoulding Linus是绝对错误的.该标准定义了哪些C结构有效.目的是告诉程序员他们能做什么或不能做什么.编译器的作用不是像Linus想要的那样"帮助"不称职的程序员.
第一个肯定是一个错误(由编译器内联实现的'malloc`导致其自身严格的别名规则犯规!).
@MichaelBurr:从根本上说,C-with-aliasing-rules和C-without-aliasing-rules应该被视为两种不同的语言,每种语言都可以做得非常好,而另一种做得不好.对于某些类型的编程,别名规则会造成大量的时间和空间惩罚,而且没有任何好处; 对于其他人来说,他们可以实现大规模的加速,而且几乎没有什 标准化记忆障碍可以实现两全其美,但即使25年也没有发生过.
Linus Torvalds有一件事是真的:标准语言不能以一种直观的方式提供可能的别名访问(例如在类型系统中编码).但明确允许类型惩罚(只是以另一种方式),所以留下的抱怨是无稽之谈.另一个可怕的事情是强制硬编码类型,因为例外打破了正交性:如何在`char`上启用TBAA?更糟糕的是在C++中:为什么`char16_t` /`char32_t`总是不能别名`wchar_t`(在C11的理智实现中不是一个麻烦,它们可能是同一整数类型的typedef名称)?
至于恐怖故事,代码片段实际上还可以.真正的错误在于memcpy.他们使用自定义memcpy(宏)使用long来复制字节.否则,完全可以将指针转换为结构,指向字符,并记忆它们.

2> paleozogt..:

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



3> Joseph Quins..:

gcc,别名和2D可变长度数组:以下示例代码复制2x2矩阵:

#include 

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的问题,因为他(即我)正在询问“合法地”使用严格的别名破坏代码的情况,而上面的内容似乎是各种各样的编译器错误。

我向海湾合作委员会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这会导致错误。

推荐阅读
小色米虫_524
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有