clang如何设法将未定义行为的代码编译到此机器代码中?

时间:2017-09-18 06:11:46

标签: c++ compilation clang compiler-optimization undefined-behavior

这是来自this tweet的代码的变体,只是较短的代码而不会对新手造成任何损害。我们有这个代码:

typedef int (*Function)();

static Function DoSmth;

static int Return7()
{
    return 7;
}

void NeverCalled()
{
   DoSmth = Return7;  
}

int main()
{
    return DoSmth();
}

你看到代码中从未调用NeverCalled(),不是吗?以下是使用

选择clang 3.8时Compiler Explorer显示的内容
-Os -std=c++11 -Wall

发出的代码是:

NeverCalled():
    retq
main:
    movl    $7, %eax
    retq

好像在NeverCalled()之前实际调用DoSmth()并将DoSmth函数指针设置为Return7()函数。

如果从NeverCalled()内部删除了函数指针赋值,如下所示:

void NeverCalled() {}

然后发出的代码就是:

NeverCalled():
    retq
main:
    ud2

后者是非常期待的。编译器知道函数指针肯定是null并且使用null函数指针调用函数是未定义的行为。

以前的代码并不是真正的预期。不知怎的,编译器决定调用Return7(),虽然它不是直接调用任何地方,函数指针赋值是在未调用的函数内部。

是的,我知道面向具有未定义行为的代码的编译器允许通过C ++ Standard执行此操作。它是如何做到的?

clang是如何发出这个特定的机器代码的?

2 个答案:

答案 0 :(得分:3)

NeverCalled用词不当。可能会调用任何全局函数(例如,通过不同转换单元中的全局对象的构造函数)。

顺便说一句,这是这个TU可能被合并到一个没有UB的程序中的唯一方法。在这种情况下,main返回7.

使NeverCalled为静态,main将编译为空代码。

答案 1 :(得分:2)

clang执行此操作的路径可能与“

”类似
  • DoSmthstatic,因此初始化为零。因为它是一个指针(对函数)具有初始化为NULL指针(或nullptr
  • 的效果
  • main()确实return DoSmth()因此DoSmth不能NULL的原因,因为这会导致return DoSmth()展示未定义的行为;
  • 然后它会解释编译单元中的其他代码,并发现DoSmth = Return7中有作业NeverCalled();
  • 由于这是编译单元中唯一将DoSmth设置为非NULL的语句,并且它认为DoSmth不是NULL,因此clang假定NeverCalled()必须是不知何故;
  • 由于上述推理,clang得出结论:DoSmth必须等于Return7的地址;
  • 由于现在推理DoSmth == Return7,clang会将return DoSmth()转换为return Return7();
  • Return7()在同一个编译单元中,所以clang内联它。

clang在内部如何做到这一点的具体细节是任何人的猜测。但是,代码优化的各个步骤可能会导致类似上述的推理链。

关键是您的代码 - 就其本身而言 - 具有未定义的行为。未定义行为的一个可爱特征是允许编译器(与要求不同)来推断您的代码实际上具有明确定义的行为。反过来,这允许编译器推断某些确保行为定义良好的代码已被神奇地执行。