我有一个关于严格别名和clang优化的问题,例如。
让我们考虑以下示例(1):
typedef void (*FTy)(void);
FTy F = 0;
(*F)();
这是一种未定义的行为。
让我们考虑下面的例子(2):
typedef void (*FTy)(void);
static const FTy F = 0;
void g( int flag)
{
if ( flag )
{
(*F)();
}
}
int main( void)
{
g( 0);
return (0);
}
F的间接呼叫仍然是“未定义的行为”,但总是处于错误的条件下。所以程序必须是正确的。
现在让我们考虑主要的例子(3):
(第二版:感谢@Antti Haapala简化版)
(第三版:使用always_inline)
#include <stdio.h>
#ifndef BUGGY
#define BUGGY 1
#endif
static inline void __attribute__((always_inline)) longLongAssign( int cond, char *ptr)
{
if ( cond )
{
*((long long *)ptr) = 0;
}
}
void funcA(int s, int g)
{
int i, j = 0, k;
int grp[4] = {-1, -1};
void *ptr[2] = {(void *)&(grp[0]), 0};
for (i = 0, k = 0; i < 1; ++i) {
for (j = 0; j < 1; ++j) {
if ( grp[g] > 0 )
{
if ( g > 5 )
{
continue;
} else
{
longLongAssign( g > 3, (char *)ptr[0]);
}
}
grp[k++] = 0;
}
printf("this should be zero: %d\n", grp[0]);
}
}
int main(void) {
funcA(0, 1);
}
由gcc编译并执行
this should be zero: 0
由“clang-7.0-O0”编译并执行
this should be zero: 0
按“clang-7.0-O1-fno strict aliasing”编译并执行
this should be zero: 0
由“clang-7.0-O1”编译并执行
this should be zero: -1
在主示例中,一个存储到grp的存储正式违反了严格的别名
*((long long *)ptr) = 0;
但这家商店总是处于错误的状态。
这里的问题是:商店如何运作
违反严格的别名规则
但位于无法到达的语句中
可能影响程序执行的任何方式?
C语言标准对吗?
下面的例子(4)是否正确、定义良好且没有未定义的行为?
void assign( int type, char *ptr)
{
if ( ptr )
{
if ( (type == 0) )
{
*((int *)ptr) = 1;
} else if ( (type == 1) )
{
*((float *)ptr) = 1;
} else
{
// unknown type
}
}
}
int main( void)
{
int a;
float b;
assign( 0, (char *)&a);
assign( 1, (char *)&b);
assign( 0, (char *)0);
return (0);
}
函数main中的内联和常量传播优化
...
if ( &a )
{
if ( (0 == 0) )
{
*((int *)&a) = 1;
} else if ( (0 == 1) )
{
*((float *)&a) = 1;
} else
{
// unknown type
}
}
...
一手店经营
*((float *)&a) = 1;
形式上违反了严格的别名,但位于不可检查的位置。
示例(4)可能不正确的原因是什么?
如果示例(4)是正确的,那么为什么示例(3)通过clang编译给出不同的结果?
最佳答案
表达式语句
*(long long *)grp = 0;
由于通过不同的不兼容类型(
int[4]
)的左值访问long long
类型的对象而具有未定义的行为,正如您在问题中观察到的,这是一种严格的别名冲突。但这并不限于运行时行为。潜在的问题在翻译时是可见的,翻译时的行为也是未定义的,因此每次执行的结果也是如此。或者至少,这是对标准的一种解释,至少有些编译器开发人员是这样认为的。这里有些人反对这样的解释,但这并不能改变你必须面对的事实。
关于最新情况
你的例子(4)有非常明确的行为。这里主要考虑的是
显式允许将一个对象指针类型的值转换为另一个对象指针类型。关于结果的对齐有一些注意事项,但是C要求它总是为转换到
char *
而工作,并且它需要反向转换来重新生成原始指针值(如果一开始是有效的,则不存在对齐问题)。允许通过字符类型的左值访问任何对象的表示。换句话说,
char *
允许对任何对象的任何部分进行别名,因此即使您不能通过传递给char *
的assign()
值直接访问任何内容,一致性编译器也必须假定这些指针可以对程序中的任何对象进行别名。任何类型的空指针都可以转换为另一个对象指针类型,从而产生目标类型的空指针。
通过以与函数实现一致的方式将
type
参数用于函数assign()
,程序确保所涉及的每个对象最终(仅)通过其正确类型的左值访问。编译器可能应用的优化与此分析无关。它是您呈现给编译器的代码,通过它可以建立行为(如果已定义)。假设程序已经定义了行为,编译器有责任确保程序在转换为可执行文件时显示该行为,并且它可以而且确实使用自己实现的知识来提供该行为。
关于c - Clang严格混叠优化与无法访问的代码违反了严格混叠,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55264205/