C++ Understanding How A Virtual Destructor Adds to the size of a Type From An Assembly Outputs Perspective

StackOverflow https://stackoverflow.com/questions/16594501

  •  29-05-2022
  •  | 
  •  

Question

I am trying to understand more deeply why the output of the following program is the way it is (see below for both). At the same time I am trying to understand how it relates to the assembly version of the program. Mainly, where are the types within the assembly code and what causes one to be larger than the other? Where in the virtual table that makes the child class larger than the test class?

CODE

#include <iostream>

class parent {
  int glove;
public:
  parent() {}
  virtual ~parent() {}
};

class child : public parent {
  int ball;
public:
  child() {}
  ~child() {}
};

class test {
  int test1;
  int test2;

public:
  test() {}
};

int main() {
  std::cout << "Size of child is " << sizeof(child) << std::endl;
  std::cout << "Size of test is " << sizeof(test) << std::endl;
}

OUTPUT

Size of child is 12
Size of test is 8

ASSEMBLY

.file   "test.cpp"
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .section    .rodata
.LC0:
    .string "Size of child is "
.LC1:
    .string "Size of test is "
    .text
.globl main
    .type   main, @function
main:
.LFB974:
    .cfi_startproc
    .cfi_personality 0x0,__gxx_personality_v0
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, 4(%esp)
    movl    $_ZSt4cout, (%esp)
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    $12, 4(%esp)
    movl    %eax, (%esp)
    call    _ZNSolsEj
    movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
    movl    %eax, (%esp)
    call    _ZNSolsEPFRSoS_E
    movl    $.LC1, 4(%esp)
    movl    $_ZSt4cout, (%esp)
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    $8, 4(%esp)
    movl    %eax, (%esp)
    call    _ZNSolsEj
    movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
    movl    %eax, (%esp)
    call    _ZNSolsEPFRSoS_E
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE974:
    .size   main, .-main
    .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB984:
    .cfi_startproc
    .cfi_personality 0x0,__gxx_personality_v0
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    cmpl    $1, 8(%ebp)
    jne .L5
    cmpl    $65535, 12(%ebp)
    jne .L5
    movl    $_ZStL8__ioinit, (%esp)
    call    _ZNSt8ios_base4InitC1Ev
    movl    $_ZNSt8ios_base4InitD1Ev, %eax
    movl    $__dso_handle, 8(%esp)
    movl    $_ZStL8__ioinit, 4(%esp)
    movl    %eax, (%esp)
    call    __cxa_atexit
.L5:
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE984:
    .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
    .type   _GLOBAL__I_main, @function
_GLOBAL__I_main:
.LFB985:
    .cfi_startproc
    .cfi_personality 0x0,__gxx_personality_v0
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $65535, 4(%esp)
    movl    $1, (%esp)
    call    _Z41__static_initialization_and_destruction_0ii
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE985:
    .size   _GLOBAL__I_main, .-_GLOBAL__I_main
    .section    .ctors,"aw",@progbits
    .align 4
    .long   _GLOBAL__I_main
    .weakref    _ZL20__gthrw_pthread_oncePiPFvvE,pthread_once
    .weakref    _ZL27__gthrw_pthread_getspecificj,pthread_getspecific
    .weakref    _ZL27__gthrw_pthread_setspecificjPKv,pthread_setspecific
    .weakref    _ZL22__gthrw_pthread_createPmPK14pthread_attr_tPFPvS3_ES3_,pthread_create
    .weakref    _ZL20__gthrw_pthread_joinmPPv,pthread_join
    .weakref    _ZL21__gthrw_pthread_equalmm,pthread_equal
    .weakref    _ZL20__gthrw_pthread_selfv,pthread_self
    .weakref    _ZL22__gthrw_pthread_detachm,pthread_detach
    .weakref    _ZL22__gthrw_pthread_cancelm,pthread_cancel
    .weakref    _ZL19__gthrw_sched_yieldv,sched_yield
    .weakref    _ZL26__gthrw_pthread_mutex_lockP15pthread_mutex_t,pthread_mutex_lock
    .weakref    _ZL29__gthrw_pthread_mutex_trylockP15pthread_mutex_t,pthread_mutex_trylock
    .weakref    _ZL31__gthrw_pthread_mutex_timedlockP15pthread_mutex_tPK8timespec,pthread_mutex_timedlock
    .weakref    _ZL28__gthrw_pthread_mutex_unlockP15pthread_mutex_t,pthread_mutex_unlock
    .weakref    _ZL26__gthrw_pthread_mutex_initP15pthread_mutex_tPK19pthread_mutexattr_t,pthread_mutex_init
    .weakref    _ZL29__gthrw_pthread_mutex_destroyP15pthread_mutex_t,pthread_mutex_destroy
    .weakref    _ZL30__gthrw_pthread_cond_broadcastP14pthread_cond_t,pthread_cond_broadcast
    .weakref    _ZL27__gthrw_pthread_cond_signalP14pthread_cond_t,pthread_cond_signal
    .weakref    _ZL25__gthrw_pthread_cond_waitP14pthread_cond_tP15pthread_mutex_t,pthread_cond_wait
    .weakref    _ZL30__gthrw_pthread_cond_timedwaitP14pthread_cond_tP15pthread_mutex_tPK8timespec,pthread_cond_timedwait
    .weakref    _ZL28__gthrw_pthread_cond_destroyP14pthread_cond_t,pthread_cond_destroy
    .weakref    _ZL26__gthrw_pthread_key_createPjPFvPvE,pthread_key_create
    .weakref    _ZL26__gthrw_pthread_key_deletej,pthread_key_delete
    .weakref    _ZL30__gthrw_pthread_mutexattr_initP19pthread_mutexattr_t,pthread_mutexattr_init
    .weakref    _ZL33__gthrw_pthread_mutexattr_settypeP19pthread_mutexattr_ti,pthread_mutexattr_settype
    .weakref    _ZL33__gthrw_pthread_mutexattr_destroyP19pthread_mutexattr_t,pthread_mutexattr_destroy
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-3)"
    .section    .note.GNU-stack,"",@progbits
Was it helpful?

Solution

Virtual methods dispatching is entirely an implementation issue (ie. compiler specific). The standard says nothing about how this should be implemented, it could be using VTables, black magic or who knows what else.

This being said, we can only guess what happens...

what causes one to be larger than the other? Where in the virtual table that makes the child class larger than the test class?

Assuming your compiler actually uses VTables, the usual way to implement them is to add a hidden pointer-to-VTable to each object if and only if the class has virtual methods (irrelevant of whether they are normal methods or the class' destructor). So your parent class would actually look like this under the hood:

class parent {
  VTable* hidden_vtable_ptr; // this is likely to be why child is bigger than test
  int glove;
public:
  parent() {}
  virtual ~parent() {}
};

Notice how this happens whether you use a virtual destructor or just a virtual method:

struct test_pod {
  int t[2];
};
struct test_dtor {
  int t[2];
  virtual ~test_dtor() {}
};
struct test_method {
  int t[2];
  virtual void foo() {}
};

int main() {
  std::cout << sizeof(test_pod) << std::endl;    // 8
  std::cout << sizeof(test_dtor) << std::endl;   // 16
  std::cout << sizeof(test_method) << std::endl; // 16
  return 0;
}

OTHER TIPS

It just adds a pointer to virtual table to your child class, which happens to be 32-bit on your particular platform.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top