假设2
源文件名为file1.c
和file2.c
。它们都由编译器单独编译,并在最后一步链接。考虑优化GCC -Ofast
。它们都有其对应的头文件,并且头文件包括彼此。
文件1:
//#include...
int bool_check;
int main(void) {
bool_check = 1;
while (bool_check) {
do_something(); //Defined in another file
}
}
文件2:
void do_something() {
extern int bool_check;
bool_check = 0;
}
在本例中,file1
调用file2
中的一个函数。
- 是否允许编译器/优化器优化
file1
中的代码,说bool_check
永远不会改变,所以我们不需要执行常量从内存读取操作? - 如果没有,如果文件是单独编译的,它如何知道
file2
中的函数修改了变量? - 如果是,这不是一个编译器bug吗?
这不是一个很好的真实用例,但很适合演示。
1条答案
按热度按时间5cnsuln71#
是否允许编译器/优化器优化file 1中的代码,说
bool_check
永远不会改变,所以我们不需要执行常量从内存读取操作?不能。只有当它能证明
bool_check
实际上没有被do_something()
的调用修改时,它才能进行优化,而这只有在它能看到代码的情况下才有可能。如果不是,如果文件是单独编译的,它怎么知道file 2中的函数修改了变量?
一般来说,它不知道。因此,它必须假设
do_something()
可能修改bool_check
,这意味着它必须在每次调用后从内存中重新加载它。一般来说,如果编译器看不到被调用函数的代码,它必须假设该函数可能读取或写入程序中的 * 每个 * 全局变量,以及每个静态,局部或动态对象,其地址已“转义”并且可能被调用函数所知。因此,在调用之前,必须将所有之前对此类对象的写入存储到内存中,并且必须在调用之后加载所有后续读取。
例如:
在调用
other2()
之后,编译器必须从内存中重新加载x
。对other1()
的调用可能将指向x
的指针隐藏在other2()
也可以访问的某个共享对象中,在这种情况下,other2()
可能通过该指针修改了x
。同样,x=3
必须将值3
实际存储到内存中,并且不能被优化掉。另一方面,
y=5
不需要存储到内存中,并且y
不需要在other2()
之后重新加载;y
的地址从未被取过,因此没有其他代码可以“知道”它的位置以修改它。实际上,y
根本不需要占用内存;它可以简单地被return
表达式中的立即常数替换。z
也是如此;即使它的地址已经被取,它也没有被传递到函数foo
之外,因此它没有“转义”。这个逻辑称为escape analysis。所以编译器可以在不改变程序的可观察行为的情况下优化它。话虽如此,现代工具链能够 * 链接时优化 *,其中编译器不仅为每个源文件(翻译单元)发出汇编代码,而且还发出一些更详细地描述代码语义的中间表示(IR)。所有这些信息都可供链接器使用,链接器可以重新运行优化和代码生成,现在整个程序立即“可见”。此时,优化器可以看到调用
do_something()
修改了bool_check
,尽管这并没有真正改变任何东西,因为编译器已经假设它是这样的。更重要的是,优化器可以看到哪些变量没有被修改,并优化掉任何死存储或不必要的重新加载。