在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年开始).我做了一个快速测试,但问题显然已经消失了.
资源:
#includestruct 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中这是否正常,但到目前为止没有人不同意.
没有我自己的恐怖故事,但这里有来自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.
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; }
gcc,别名和2D可变长度数组:以下示例代码复制2x2矩阵:
#includestatic 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
这会导致错误。