gcc,严格的别名和恐怖故事
在gcc-strict-aliasing-and-casting-through-a-union中,我询问是否有任何人遇到指向工会的问题。 到目前为止,答案似乎是没有 。
这个问题更广泛:你有没有关于gcc和严格别名的恐怖故事?
背景: 在c99-strict-aliasing-rules-in-c-gcc中引用AndreyT的答案 :
“严格的锯齿规则植根于标准化时代以来存在于C和C ++中的标准的一部分,C89 / 90(6.3)中存在禁止通过另一种types的左值访问一种types的对象的子句。 )以及C ++ 98(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 。
其他一些不太相关的链接:
- 业绩冲击的-FNO严格走样
- 严格走样
- 当-是炭安全换严格指针走样
- 如何对检测严格走样,在编译时
所以要重复一遍,你有自己的恐怖故事吗? 没有用“ -Wstrict-aliasing
表示的问题当然是优选的。 而其他的C编译器也是受欢迎的。
6月2日新增 :迈克尔·伯尔(Michael Burr)的答案中的第一个链接确实是一个恐怖故事,或许有点过时(从2003年开始)。 我做了一个快速testing,但问题显然已经消失。
资源:
#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复制到stream中)。
编译代码,在CYGWIN上使用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
对于迈克尔的回答中的第二个环节,
*(unsigned short *)&a = 4;
gcc通常会(总是?)给出警告。 但我相信一个有效的解决scheme(对于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中是否可以 ,但是到目前为止没有人反对。
没有我自己的恐怖故事,但是这里有一些来自Linus Torvalds的引语(抱歉,如果这些已经在这个问题的链接引用之一):
http://lkml.org/lkml/2003/2/26/158 :
date星期三,2003年2月26日09:22:15 -0800主题Re:无编译没有-fno-strict-aliasing从Jean Tourrilhes <>
在2003年2月26日星期三下午04:38:10 +0100,Horst von Brand写道:
让·图里里斯<>说:
它看起来像一个编译器bug …我有些用户抱怨说,当下面的代码编译没有-fno-strict-aliasing,写入和memcpy的顺序是颠倒的(这意味着一个假的len是mem-copied进入stream)。 代码(来自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; }
恕我直言,编译器应该有足够的上下文知道重新sorting是危险的。 任何build议,使这个简单的代码更加防弹是值得欢迎的。
由于严格的别名,编译器可以自由地假定char *stream和struct iw_event * iwe指向单独的内存区域。
这是真的,哪个不是我所抱怨的问题。
而Linus Torvald对上述的评论:
Jean Tourrilhes写道:>
它看起来像一个编译器错误,我…
你为什么认为内核使用“-fno-strict-aliasing”?
海湾合作委员会的人更有兴趣试图找出什么可以允许的C99规格,而不是实际工作 。 特别是别名代码甚至不值得启用,当某些事情可以混淆时,无法正确地告诉gcc。
有些用户抱怨说,当下面的代码编译没有-fno-strict-aliasing时,write和memcpy的顺序是相反的(这意味着一个假的len被mem复制到stream中)。
这个问题是我们内联memcpy(),在这一点上gcc不会关心它可以别名的事实,所以他们只是重新排列所有东西,并声称它是自己的错。 尽pipe我们甚至没有理智地告诉gcc。
几年前我试图find一个理智的方式,而海湾合作委员会的开发人员真的不关心这个领域的真实世界。 如果这种情况发生了变化,我会感到惊讶的。
我不打算去打。
莱纳斯
http://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg01647.html :
基于types的别名是愚蠢的 。 这太令人难以置信了,甚至不好笑。 它坏了。 海湾合作委员会接受了这个破碎的概念,并且把它变成一个“没有意义的法律”的东西。
…
我知道一个事实 ,即海湾合作委员会将重新命令写入访问清楚地(静态)相同的地址。 Gcc会突然想到这一点
unsigned long a; a = 5; *(unsigned short *)&a = 4;
可以重新sorting,先将它设置为4(因为显然它们没有别名 – 通过阅读标准),然后因为现在“a = 5”的赋值在后面,所以4的赋值可以完全消失! 如果有人抱怨编译器是疯狂的,编译器的人会说“nyaah,nyaah,标准的人说我们可以做到这一点”,绝对没有反省问是否有任何SENSE。
SWIG生成的代码依赖严格的别名closures,这可能会导致各种问题 。
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; }
gcc,别名和二维可变长度数组:以下示例代码复制一个2x2matrix:
#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)
我不知道这是否一般是已知的,我不知道这是一个错误还是一个function。 我不能在Cygwin上复制gcc 4.3.4的问题 ,所以可能已经修复了。 一些解决方法:
- 使用
__attribute__((noinline))
作为copy()。 - 使用gcc开关
-fno-strict-aliasing
。 - 将copy()的第三个参数从
b[][n]
更改为b[][2]
。 - 不要使用
-O2
或-O3
。
进一步说明:
- 对于我自己的问题,这是一年一天之后的答案(对于只有两个其他答案,我感到有些惊讶)。
- 我在实际的代码卡尔曼滤波器上丢失了几个小时。 看起来很小的变化可能会产生很大的影响,也许是因为改变了gcc的自动内联(这是一个猜测;我仍然不确定)。 但它可能不符合恐怖的故事 。
- 是的,我知道你不会像这样写
copy()
。 (另外,我有点惊讶地看到gcc没有展开双循环。) - 没有gcc警告开关,包括
-Wstrict-aliasing=
,在这里做了什么。 - 一维可变长度的数组似乎是确定的。
更新 :上面没有真正回答OP的问题,因为他(即我)在询问严格别名“合法”破坏了你的代码的情况,而上面看来只是一个花园式的编译器错误。
我向GCC Bugzilla汇报过,但他们对旧版本4.1.2并不感兴趣,尽pipe(我相信)它是10亿美元RHEL5的关键。 这在4.2.4中不会发生。
我有一个稍微简单的例子,类似的错误,只有一个matrix。 代码:
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
。
下面的代码在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; }
这是我的:
http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html
它导致CAD程序中的某些形状被错误地绘制。 感谢项目领导者为build立回归testing套件所做的努力。
该错误只在某些平台上performance出来,包括旧版本的GCC和某些库的旧版本。 然后只有-O2打开。 -fno-strict-aliasing解决了它。
C的公共初始序列规则被解释为可以编写一个可以在各种结构types的前导部分工作的函数,只要它们从匹配types的元素开始。 在C99下,规则被改变了,所以只有当涉及的结构types是完全声明在使用点可见的同一联合的成员时才应用。
gcc的作者坚持认为,只有通过工会types进行访问,这种语言才适用,尽pipe事实如下:
-
如果必须通过联合types执行访问,则没有理由指定完整声明必须可见。
-
尽pipe独联体规则是以工会的forms来描述的,但它的主要用处在于暗示结构布局和访问的方式。 如果S1和S2是共享一个CIS的结构,那么从外部源接受一个指向S1和S2的指针的函数就不可能符合C89的CIS规则,而不会允许相同的行为对指向实际上不在联合对象内的结构; 规定独联体对结构的支持因此是多余的,因为它已经被指定给工会。