声明抽象类(纯虚方法)会大幅增加二进制文件的大小

12
这是故事背景: 我正在Linux中使用AC6 Toolpack为ARM Cortex-M0处理器开发C++软件。之前我使用Keil(在windows上)(他们有自己的工具链),现在我已经迁移到GNU-toolchain(GNU工具套件 for ARM嵌入式处理器5.2.1)。第一件事我意识到的是,二进制文件大小显著增加。 我测试了每种编译器优化(除了链接时优化,它会在内联汇编时出错,不是问题的一部分,但可能与答案相关)。然后开始使用任何可用的工具来检查可执行文件(elf文件而不是bin文件,gnu可以生成两个):objdump、readelf、nm。我发现一些符号导致了大小的增加,其中最重要的是:“d_print_comp_inner”,“d_exprlist”和“d_template_args”。但我不知道什么导致这些函数出现在二进制文件中。(我只使用了最小限度的库:nano newlib)。长话短说,我开始逐个消除代码,试图找到问题所在。最后,我发现问题出在抽象方法声明上! 将函数定义为
virtual Return_type function_name(...)=0;

替代

 virtual Return_type function_name(...);

添加了45 KB和我提到的符号。 这是源代码中唯一的更改。在基类中存在空定义。请注意:该方法仍然是虚拟的,并在子类中被覆盖

没有抽象类的大小输出:

   text    data     bss     dec     hex filename
  15316      24    4764   20104    4e88 temc_discovery.elf

使用抽象类进行大小输出:

   text    data     bss     dec     hex filename
  61484     128    4796   66408   10368 temc_discovery.elf

以下是方法为抽象方法时出现的符号及其大小,已删除在两个版本中都出现的符号。(使用nm工具。不完整列表,仅包含大小>=0x60的符号)

00002de4  t d_print_comp_inner
00001a34  t d_exprlist
00000ca4  t d_template_args
00000678  t d_type
00000574  t d_print_mod
000003f8  t d_encoding
000003e0  r cplus_demangle_operators
000003c8  t d_expression_1
000003a8  t d_name
00000354  t d_demangle_callback.constprop.15
000002e0  t d_print_mod_list
00000294  r cplus_demangle_builtin_types
00000268  t d_unqualified_name
00000244  T _printf_i
00000238  t d_print_function_type.isra.11
000001fc  T _svfprintf_r
000001fc  T _svfiprintf_r
000001f4  t d_print_array_type.isra.10
000001ce  t d_print_cast.isra.12
0000018c  t d_substitution
00000110  t d_operator_name
0000010c  T __sflush_r
000000e8  T __swsetup_r
000000e6  t d_cv_qualifiers
000000e0  t d_print_subexpr
000000e0  t d_expr_primary
000000dc  T _printf_common
000000cc  T __cxa_demangle
000000c8  t d_source_name
000000c4  r standard_subs
000000c4  T __ssputs_r
000000b0  T __swbuf_r
000000ac  T _malloc_r
000000a8  T _fputs_r
000000a4  T __smakebuf_r
000000a0  T __gnu_cxx::__verbose_terminate_handler()
00000096  t d_print_expr_op
0000008c  T _free_r
0000008c  t d_parmlist
0000008a  t d_growable_string_callback_adapter
0000007c  T __sfp
00000072  t d_append_buffer
00000068  T __sinit
00000060  d impure_data

我在源代码中看不到一些我熟悉的名称(如printf、flush、malloc、fputs等)。

有人知道是什么原因导致了这种行为吗?

更新: 我已经使用标志--noexception禁用了异常,所以我没有考虑过它。事实证明,它与答案有关,所以在这里提一下。

更新2: 这是最全面的网站,解释了所有问题,如果你追踪答案中的链接。


最好提供编译和链接命令,例如使用“-g”选项将生成带有调试符号的较大二进制文件。您还可以检查剥离二进制文件的大小。 - Mine
就像我之前说的,我已经尝试了所有编译器优化。结果仍然相同(甚至增加了40 KB)。 - ifyalciner
2
解决方案可能已经在这里给出:https://dev59.com/d27Xa4cB1Zd3GeqPlQz6 - user2512323
@deniss 我也是,禁用了异常处理,之前没有考虑过这个问题。让我尝试应用那个解决方案。 - ifyalciner
@deniss 谢谢你的回答。它起作用了。 - ifyalciner
1
通常情况下,你需要同时使用-fno-exception-fno-rtti来显著减少程序的体积。请注意,默认情况下,new分配器会抛出异常。显然,-fno-rtti至少解决了链接的问题中一个帖子的问题。 - artless noise
2个回答

8
这几乎肯定是由于意外地包含了异常处理,而libc++已经内置了它,无论您是否使用--noexception或其他正确的gnu命令进行编译。

问题中可能会出现“纯虚函数调用”之类的异常(如果您在基类构造函数中调用虚函数,则可能会出现相当晦涩的运行时错误)。

答案是提供自己的空实现,atexit()和任何您不真正需要的随机调用。一旦您这样做了,链接器就不会拉入其他东西(这会拉入其他东西,这会拉入其他东西等等)。

void __cxa_pure_virtual(void) 
{ 
    BKPT();
}

这是我们项目中使用的内容,但您使用的libc++版本可能已经发生了变化。


4
据我所知,当在基类中将虚函数声明为纯虚函数时,你就会创建潜在的纯虚函数调用。因此,编译器生成的代码将打印与纯虚函数调用相关的消息、函数和类的反汇编名称以及其他信息。这会为您的二进制文件添加一堆函数,从而增加大小。
建议你向你的纯虚函数添加空实现 - 可能可以防止编译器执行那些操作。

代码中唯一的变化是 '=0'。为了避免出现“函数未定义错误”,我已经提前定义了一个空函数。 - ifyalciner
请参见https://dev59.com/gXVD5IYBdhLWcg3wE3Ro。 - Russ Schultz
@ifyalciner,你可以在函数和其空实现中都使用=0,例如虚析构函数。 - user2807083

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接