编译器如何知道不将关键部分内的代码移动到关键部分的外部?

时间:2017-03-08 22:56:13

标签: c winapi

根据我的理解:

  • 编译器可以对我的代码进行任何重新排序。

  • 关键部分内的代码不会移动到关键部分的外部。

现在说我有以下代码:

printf("Hi");

EnterCriticalSection(&CriticalSection);
printf("Inside the critical section");
printf("Also inside the critical section");
LeaveCriticalSection(&CriticalSection);

printf("Bye");

现在,编译器是否会实际查找函数EnterCriticalSection()LeaveCriticalSection(),而不是将它们内部的代码移到外面?

2 个答案:

答案 0 :(得分:6)

  

编译器可以对我的代码进行任何重新排序。

这是不正确的。编译器是有限的。

  

关键部分内的代码不会移动到关键部分的外部。

这也是不正确的,具体取决于关键部分内/外的代码。

优化和约束

编译器对函数中的每段代码都有一堆约束。这些约束可能是输入和输出(除非你先执行X,否则不能执行Y)或更多的通用语句,如"这会影响某处内存的内容"。编译代码时,编译器将保留这些约束。如果编译器不知道函数是什么,它将使用最重的约束集。

通常,这意味着编译器不会翻转两个函数调用 1 的顺序。

f(); // Maybe affects memory somewhere.
g(); // Maybe affects memory somewhere.

这也意味着通常,如果访问内存,那些内存访问必须相对于函数调用进行排序。

void myfunc(X *ptr) {
    my_lock(ptr->mutex);   // Maybe affects memory somewhere.
    ptr->field++;          // Definitely affects memory at ptr->field.
    my_unlock(ptr->mutex); // Maybe affects memory somewhere.
}

但是,它可以对关键部分的内容进行重新排序:

int i = 5;
my_lock();   // Maybe affects memory somewhere.
i++;         // This is not "memory somewhere", this is my variable,
             // it's a register or on the stack and nobody else can
             // change it.
my_unlock(); // Maybe affects memory somewhere.

因此可以将其重新排序为:

int i = 6;
my_lock();
my_unlock();

上面的代码可以重新排序,因为编译器对允许修改i的人有特殊的了解。如果你在其他地方有一些特殊代码,它们会在堆栈中试图创建一个指向i的指针,即使程序中没有出现&i,你也会违反与编译器的合同(又名你的程序有未定义的行为)。

我希望这能澄清事情。

脚注

1 :您可以添加更改此规则的__declspec(noalias)等注释,LTO /整个程序优化/过程间优化也可以改变这一点。

答案 1 :(得分:0)

假设编译器没有任何关于EnterCriticalSection的特殊知识;它无法对函数的作用做出任何假设。

因此,它无法对printf语句重新排序,因为它必须允许EnterCriticalSection也可能写入stdoutstdout上的输出必须出现与执行printf语句的顺序相同。

这同样适用于访问全局变量之类的事情,编译器必须允许函数访问这些变量。