GCC:严格别名警告的准确性
我正在尝试检查我的一些代码是否存在严格的别名违规,但在尝试理解严格的别名规则时,我似乎错过了一些东西。
想象一下以下代码:
#include int main( void ) { unsigned long l; l = 0; *( ( unsigned short * )&l ) = 1; printf( "%lu\n", l ); return 0; }
经典和基本的例子。 使用GCC 4.9( -Wall -fstrict-aliasing -Wstrict-aliasing -O3
),它实际报告错误:
dereferencing type-punned pointer will break strict-aliasing rules
但以下编译很好:
#include int main( void ) { unsigned long l; unsigned short * sp; l = 0; sp = ( unsigned short * )&l; *( sp ) = 1; printf( "%lu\n", l ); return 0; }
在我的理解中,第二个例子也违反了结构别名规则。
那为什么要编译呢? 这是GCC中的准确性问题,还是我错过了严格混叠的内容?
我还发现了以下主题: 为什么没有为此代码生成严格别名警告?
使用-Wstrict-aliasing=2
或-Wstrict-aliasing=3
进行编译没有任何区别。
但是-Wstrict-aliasing=1
会在第二个示例中报告错误。
海湾合作委员会的文件说1级是最不准确的,可以产生很多误报,而3级是最准确的……
那么这里发生了什么? 我自己理解的问题还是GCC的问题?
奖金问题
对于我的项目,我通常更喜欢Clang / LLVM而非GCC,但似乎Clang没有发出任何关于严格别名的警告。
有谁知道为什么?
是因为它无法检测到违规,或者因为它在生成代码时不遵循规则?
你的理解是正确的。 别名分析通常很复杂,在这种情况下,显然仅使用强制转换和取消引用之间的临时指针就足以将其抛弃。 令人惊讶的是,GCC 4.8.2在此代码上做得更好,警告-Wstrict-aliasing=2
以及1级,因此这是一个回归。
至于clang,它目前根本没有设备来警告违规行为。 它确实利用了优化中的规则。 要看到这一点,请直接从C标准(N1570§6.5.2.39)中取这个例子)
struct t1 { int m; }; struct t2 { int m; }; int f(struct t1 *p1, struct t2 *p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; }
如果p1和p2指向相同的结构,则Clang(和GCC)将在否定之前返回p1->m
的值,因为它们可能假设p2不会对p1进行别名,因此先前的否定决不会影响结果。 这是带有和不带-fstrict-aliasing
的完整示例和输出。 有关更多示例,请参阅此处以及经常引用的每个C程序员应该知道的关于未定义行为的内容 ; 严格的别名优化是介绍性post的最后一个主题。
至于什么时候警告会被实现, 开发人员很安静 ,但是在clang的测试套件中提到了它们,它在标题下列出了-Wstrict-aliasing=X
(强调我的)
这些标志目前尚未实施; 测试我们输出它们无论如何。
所以似乎有可能在某个时刻发生。
没有什么本质上的错误
sp = ( unsigned short * )&l;
对于所有编译器都知道的,您可能只是再次将指针强制转换为正确的类型。
也没有什么本质上的错误
*( sp ) = 1;
这是两者的结合,这是错误的。
编译器只是不能把两者放在一起告诉你组合是有问题的(在一般情况下是不可能的,在这种特殊情况下不是那么容易)。
但
*( ( unsigned short * )&l ) = 1;
更容易检测,因此编译器尽力这样做。
我希望在O3编译器决定, 因为它知道不同类型的变量不能别名 ,所以从不使用sp
。 因此,它从程序中删除sp
并且不会产生错误。
要么是因为l
是已知值,要么将其转换为常量以供printf使用。 它仍然具有存储空间,因为它的地址已被占用,但该存储空间永远不会被写入。
我对情况的解释:
-
*( ( unsigned short * )&l ) = 1;
更容易捕获,因为违规是一行。 所以它总是被抓住了。 -
通过优化,第二个变体也被捕获。 优化后,它就像第一个一样简单。
-
如果没有优化,
-Wstrict-aliasing=1
(比默认值更加懒惰)会捕获它。 这是因为它将转换本身视为违规,即使没有取消引用(删除取消引用并查看)。 这种简单的逻辑很快,但会导致误报。