Clang如何设法将其用未定义的行为编译到此计算机代码中

How does clang manage to compile this code with undefined behavior into this machine code?

本文关键字:编译 代码 计算机 未定义 Clang      更新时间:2023-10-16

这是来自此推文的代码变体,只是较短,不会造成任何损坏NOBS。我们有此代码:

typedef int (*Function)();
static Function DoSmth;
static int Return7()
{
    return 7;
}
void NeverCalled()
{
   DoSmth = Return7;  
}
int main()
{
    return DoSmth();
}

您看到NeverCalled()从未在代码中调用,不是吗?这是编译器资源管理器在选择

的Clang 3.8时显示的内容
-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 标准的编译器面向不确定行为的编译器。它是如何做到的?

叮当声如何发射此特定的机器代码?

NeverCalled是错误的指示。任何全局函数都可能被调用(例如,由不同翻译单元中的全局对象的构造函数)。

顺便说一句,这是该图可能会纳入没有UB的程序中的唯一方法。在这种情况下,main返回7。

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

clang做的路径可能是;

的行
  • DoSmthstatic,因此初始化为零。由于它是一个指针(函数),具有初始化对NULL指针(或nullptr
  • 的效果
  • main()return DoSmth()因此, DoSmth不能为 NULL的原因,因为这会导致 return DoSmth()表现出不确定的行为;
  • 然后在编译单元中有关其他代码的原因,发现NeverCalled()中有一个分配DoSmth = Return7;
  • 因为这是汇编单元中唯一的语句将DoSmth设置为非null,并且它认为DoSmth不是null,所以Clang假设NeverCalled()必须以某种方式称为;
  • >
  • 由于上述推理的结果结论是DoSmth必须等于Return7的地址;
  • 由于现在已经认为DoSmth == Return7,Clang将return DoSmth()转换为return Return7();
  • Return7()在同一编译单元中

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

关键是您的代码(如它所处的代码)具有不确定的行为。未定义行为的一个可爱功能是,允许编译器(与要求不同),以理论您的代码实际上具有明确定义的行为。反过来,这允许编译器认为确保有明确定义的行为的某些代码被神奇地执行。