Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring abstract class (pure virtual method) increase binary size substantially

Here is the story: I am developing C++ software for ARM Cortex-M0 processor in Linux with AC6 Toolpack. Before I was using Keil (in windows) (who has their own toolchain) and I have migrated to GNU-toolchain ((GNU Tools for ARM Embedded Processors) 5.2.1) . First thing i have realized is; binary file size increased substantially. I have tested every compiler optimizations (except link time optimization, it gives an error in inline assembly, not part of question but may be related to answer). Then started to inspect executables (elf file not bin, gnu produces both) with any tool available :objdump, readelf, nm. I found some symbols causing size increase, significant ones are: 'd_print_comp_inner' , 'd_exprlist', 'd_template_args'. But have no idea what is causing these functions to appear in binary. (i have used minimal libraries: nano newlib). Long story short I started to eliminate codes one by one to find culprit. At last it was Abstract Method declaration!

Defining function as

virtual Return_type function_name(...)=0;

instead of

 virtual Return_type function_name(...);

adding 45 KB and the symbols I have mentioned. And this is the only change in source code. Empty definition in base class is present. Note that: method is still virtual and overridden in child classes

Size output without Abstract Class:

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

Size output with Abstract Class:

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

Here the symbols and theirs size that shows up when method is abstract, eliminated the ones show up in both versions. (nm tool is used. Not complete list, the ones with size >=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

Some names familiar to me (such as printf,flush,malloc,fputs etc.) are not even mentioned in the source code.

Any one with any idea what is causing this behavior?

Update: I was already disabling exceptions with flag --noexception, so I haven't given any though to it. As it turns out, it is related to answer so good to mention this here.

Update 2: This is the most comprehensive website explaining it all, if you track the links in answers.

like image 256
ifyalciner Avatar asked Jun 21 '16 14:06

ifyalciner


2 Answers

It's almost certainly because of unexpected inclusion of exception handling, which libc++ has built into it, regardless of whether or not you compile your code with --noexception or whatever the proper gnu-ism is.

The exception in question is probably 'pure virtual function call' or something like that (a fairly obscure runtime error to get, but possible if you call virtual functions in the base class constructor).

The answer is to provide your own empty implementation of this, atexit(), and whatever random callout that you don't really need. Once you do that, the linker won't drag in the other stuff (which drags in other stuff, which drags in other stuff, etc).

void __cxa_pure_virtual(void) 
{ 
    BKPT();
}

Is what I have on our project, though things may have changed in your version of libc++

like image 149
Russ Schultz Avatar answered Sep 28 '22 13:09

Russ Schultz


As far as I understand, when you make your virtual function in base class pure you create the potential for making a pure virtual call. So compiler generates code where it prints message about pure virtual call fact, demangled names for function and class and may be something else. It adds bunch of functions to your binary for that, so size increases.

I suggest to add empty implementation to your pure virtual function - may be it prevents compiler from doing that stuff.

like image 36
user2807083 Avatar answered Sep 28 '22 13:09

user2807083