代码中未显式调用的format_disk为何会在运行时被执行?
为什么未显式调用的
format_disk函数在Clang优化编译时会被执行? 嘿,这个问题挺有意思的,本质是C++里**未定义行为(Undefined Behavior)**带来的编译器差异,咱们来掰扯清楚:
首先先看你给出的代码:
#include <cstdio> static void format_disk() { std::puts("formatting hard disk drive!"); } static void (*foo)() = nullptr; void never_called() { foo = format_disk; } int main() { foo(); }
核心问题:调用空函数指针属于未定义行为
这段代码里,main()直接调用了初始化为nullptr的函数指针foo——这是C++标准明确规定的未定义行为(UB)。什么是未定义行为?简单说就是标准不要求编译器怎么处理这种情况,编译器可以随便来:崩溃、执行奇怪的代码、甚至啥都不做,全看编译器的优化策略和实现逻辑。
Clang的优化逻辑:为“合理”推断代码路径
在-O3的高优化级别下,Clang会做一些激进的代码分析:
- 编译器发现
never_called()是整个程序里唯一给foo赋值的函数。如果程序最终调用了foo,那从逻辑上看,never_called()肯定被执行过(否则foo是空指针,调用它就是UB)。 - Clang默认程序员不会故意写UB代码,于是它直接推断
never_called()必须被执行,进而把never_called()的逻辑内联到程序中,让foo指向format_disk,最终导致format_disk被执行。 - 说白了,Clang把“调用空指针”这个UB情况,转化为“既然你调用了foo,那foo必然被赋值过,所以自动补上赋值逻辑”。
GCC的处理方式:严格按字面代码执行
GCC在同样的-O3优化级别下,没有做这种推断:
- 它严格按照代码字面意思处理:
never_called()从来没被调用过,foo一直是nullptr,main()里调用空函数指针直接触发段错误——这是符合大多数人直觉的结果,但本质上也是编译器对UB的一种处理方式。
实际编译运行的命令和输出
Clang编译运行:
$ clang++ -std=c++17 -O3 a.cpp && ./a.out formatting hard disk drive!
GCC编译运行:
$ g++ -std=c++17 -O3 a.cpp && ./a.out Segmentation fault (core dumped)
编译器版本信息:
- Clang:
$ clang --version clang version 5.0.0 (tags/RELEASE_500/final) Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /usr/bin
- GCC:
$ gcc --version gcc (GCC) 7.2.1 20171128 Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
总结
这个现象的根源就是调用空函数指针的未定义行为,不同编译器对UB的优化处理策略不一样:Clang选择了让程序“看似合理运行”的路径,而GCC则直接暴露了空指针调用的错误。写C++代码时一定要避免未定义行为,否则程序的行为完全不可预测,不同编译器、甚至同一编译器不同优化级别下的表现都可能天差地别。
内容的提问来源于stack exchange,提问作者Mário Feroldi
相关产品推荐
相关产品推荐

