我有一个关于严格混叠和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编译给出不同的结果?
答案 0 :(得分:2)
表达式语句
正如您在问题中所观察到的,*(long long *)grp = 0;
由于通过不同的不兼容类型(int[4]
)的左值访问类型long long
的对象而具有未定义的行为-严格混叠。但这并不仅限于 runtime 行为。 (潜在的)问题在翻译时可见,翻译时行为也未定义,因此每次执行的结果也是如此。
至少,这至少是一些编译器开发人员所遵循的标准的解释。这里周围的一些人反对这样的解释,但这并没有改变您必须处理它们的事实。
关于更新
您的示例(4)具有完美定义的行为。这里的主要考虑因素是
明确允许将一种对象指针类型的值转换为另一种对象指针类型。关于结果对齐有一些警告,但C要求它始终能够工作以转换为char *
,并且它需要反向转换以重现原始指针值(如果以开头有效,则没有对齐问题。 )。
允许通过字符类型的左值访问任何对象的表示形式。换句话说,允许char *
别名任何对象的任何部分,因此即使您不要直接通过传递给{{1 }},合格的编译器必须假定这些指针可以为程序中的任何对象起别名。
任何类型的空指针都可以转换为另一种对象指针类型,从而产生目标类型的空指针。
通过以与该函数的实现一致的方式对函数char *
使用assign()
参数,该程序确保最终(仅)通过其左值访问每个涉及的对象正确的类型。
编译器可以应用哪些优化与该分析无关。它是您提供给编译器的代码,通过该代码可以确定行为(如果已定义)。假设程序已经定义了行为,则编译器的职责是确保程序将行为表现为翻译成可执行文件的结果,并且可以并且确实将其有关自己实现的知识用于为此。
答案 1 :(得分:0)
谢谢大家的评论!您已经帮助我更好地理解了问题。
仅说明我为什么这样做以及我真正想要的是: 我正在某些特定平台上移植clang。因此,我的目标是了解此测试(来自我们的autogen测试系统)是否包含错误,或者说是clang编译错误。根据讨论的结果,我提交了一个llvm错误 (https://bugs.llvm.org/show_bug.cgi?id=41178)。
再次感谢!