C 编译器如何知道不将关键部分内的代码移动到关键部分外?
根据我的理解:C 编译器如何知道不将关键部分内的代码移动到关键部分外?,c,winapi,C,Winapi,根据我的理解: 编译器可以对我的代码进行任何重新排序 临界区内的代码不会移动到临界区外 现在假设我有以下代码: printf("Hi"); EnterCriticalSection(&CriticalSection); printf("Inside the critical section"); printf("Also inside the critical section"); LeaveCriticalSection(&CriticalSection); print
- 编译器可以对我的代码进行任何重新排序
- 临界区内的代码不会移动到临界区外
printf("Hi");
EnterCriticalSection(&CriticalSection);
printf("Inside the critical section");
printf("Also inside the critical section");
LeaveCriticalSection(&CriticalSection);
printf("Bye");
现在,编译器是否真的会查找函数
EnterCriticalSection()
和LeaveCriticalSection()
,而不会将其中的代码移到外部?假设编译器对EnterCriticalSection没有任何专门的知识;它无法对函数的功能做任何假设
因此,它不能对printf
语句进行重新排序,因为它必须考虑EnterCriticalSection
也可能写入stdout
,并且stdout
上的输出必须以与printf
语句被写入时相同的顺序显示
这同样适用于全局变量的访问,编译器必须允许函数可能访问这些变量。假设编译器没有任何关于EnterCriticalSection的特殊知识;它无法对函数的功能做任何假设 因此,它不能对
printf
语句进行重新排序,因为它必须考虑EnterCriticalSection
也可能写入stdout
,并且stdout
上的输出必须以与printf
语句被写入时相同的顺序显示
这同样适用于访问全局变量,编译器必须允许函数访问这些变量
编译器可以对我的代码进行任何重新排序
这是不正确的。编译器是有限的
临界区内的代码不会移动到临界区外
这也不正确,这取决于关键部分内部/外部的代码
优化与约束
编译器对函数中的每段代码都有一系列约束。这些约束可能是输入和输出(除非先执行X,否则不能执行Y)或更一般的语句,如“这会影响内存的某个地方的内容”。编译器将在编译代码时保留这些约束。如果编译器不知道函数做什么,它将使用最重的约束集
通常,这意味着编译器不会翻转两个函数调用的顺序1
这还意味着,一般来说,如果访问内存,这些内存访问必须相对于函数调用进行排序
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:您可以添加注释,如\uu declspec(noalias)
,这些注释可以更改此规则,LTO/整个程序优化/过程间优化也可以更改
编译器可以对我的代码进行任何重新排序
这是不正确的。编译器是有限的
临界区内的代码不会移动到临界区外
这也不正确,这取决于关键部分内部/外部的代码
优化与约束
编译器对函数中的每段代码都有一系列约束。这些约束可能是输入和输出(除非先执行X,否则不能执行Y)或更一般的语句,如“这会影响内存的某个地方的内容”。编译器将在编译代码时保留这些约束。如果编译器不知道函数做什么,它将使用最重的约束集
通常,这意味着编译器不会翻转两个函数调用的顺序1
这还意味着,一般来说,如果访问内存,这些内存访问必须相对于函数调用进行排序
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:您可以添加注释,如
\uu declspec(noalias)
,这些注释可以更改此规则,LTO/整个程序优化/过程间优化也可以更改内容。编译器对您的关键部分一无所知。它在更微观的层面上运作。它在装配级别运行。它可能会将单个装配说明移动到关键部分之外。但它不会影响代码的正确性,因为这是它的核心保证之一。它非常简单,不能进行“任何重新排序”。优化器不会在它不知道的函数调用之间移动代码。它必须假设它有明显的副作用。就像它永远不会在“Hi”之前打印“Bye”。@Hans Passant所以即使我使用了foo()
和boo()
而不是EnterCriticalSection()