编译gcc标志-fno-exceptions会减少可执行二进制文件的大小吗?



在阅读gcc手册中关于异常处理和编译器标志-fno-exceptions的部分时,我遇到了以下几行:

异常处理开销可以通过可执行二进制文件,并随底层的能力而变化操作系统和c++编译器的具体配置。在最近的硬件与同时代的GNU系统软件相结合启用异常处理所需的代码和数据大小开销是存在的7%。

我试图通过使用Ubuntu 20.04和gcc 10.3.0编译一些简单的c++程序(没有异常抛出代码)来重现这个开销,无论是否使用-fno-exceptions标志,但是无法观察到任何关于编译的二进制可执行文件大小的差异。

所以我得出的结论是,从手册引用的句子是指仅在用-fno-exceptions重新编译libstdc++文件时产生的二进制文件,因为在这种情况下,try,catchthrow的每一个出现将被if取代…else分支。

我不完全确定这一点,所以这里是我的问题:

a)使用-fno-exceptions编译的用户代码只阻止使用关键字try,catchthrow,而不会自己生成更小的二进制文件,对吗?

b)用-fno-exceptions编译的用户代码仍然可以暴露于从libstdc++函数抛出的异常,如果这些没有(重新)用-fno-exceptions编译,对吗?

c)使用-fexceptions(默认)编译的用户代码确实会产生更大的二进制文件,因为生成的帧展开信息,但只有在实际使用异常时,对吗?

可以减小二进制文件的大小,对于较大的程序通常这样做。但是,不能保证总是这样。

a)使用-fno-exceptions编译的用户代码只会阻止使用关键字try, catch和throw,并且不会自己生成更小的二进制文件,对吗?

不,它肯定对代码生成有影响。然而,异常不会不分青红皂白地增加代码大小。生成的框架展开代码必须涉及可能的异常。在展开期间还必须有一些事情要做(即非平凡的析构函数)。如果在给定的函数中不存在其中一个,则-fno-exceptions将不会对该函数产生影响。

例如,编译以下代码将清楚地显示-fno-exceptions的代码大小更小。

#include <vector>
void foo(); // could potentially throw.
void bar() {
std::vector<int> v(12); // has non-trivial destructor.
foo();
}

参见on godbolt

注意下面的每一个修改是如何消除异常处理代码的:

  • foo()的声明更改为void foo() noexcept;.
  • 在同一个TU中提供foo()的非抛出实现:void foo() {}
  • 将向量的构造移动到foo()调用之后。

b)使用-fno-exceptions编译的用户代码仍然可以暴露于从libstdc++函数抛出的异常,如果这些没有使用-fno-exceptions(重新)编译,对吗?

libstdc++抛出的任何异常都将导致使用-fno-exceptions编译的代码立即终止。如果这算"暴露"的话。对你来说,是的。

但是,请记住libstdc++的很大一部分是直接在头文件中实现的,并且您的编译器标志将应用于库的这一部分。

c)使用-fexceptions(默认)编译的用户代码确实会产生更大的二进制文件,因为生成的帧展开信息,但只有在实际使用异常时,对吗?

接近但不完全接近。该代码将在任何可能抛出异常的地方发出。这包括对没有noexcept的函数的任何调用,该函数定义在与正在编译的TU不同的TU中。

最新更新