I want to make sure I am understanding what my code is actually being compiled down to before an exe/library is made from it. I have the following program written in C++98. Which stems from this website http://www.phpcompiler.org/articles/virtualinheritance.html .
#include <stdio.h>
class top
{
public:
int t;
};
class left : virtual public top
{
public:
int l;
};
class right : virtual public top
{
public:
int r;
};
class bottom : public left, public right
{
public:
int b;
};
int main()
{
bottom *b = new bottom();
b->l = 5;
left *l = b;
printf("%d\n", l->l);
}
The assembly output, compiled with, g++ -S main.cpp
, is below with comments on how I think it should be broken down (this is where I will need some schooling) as well as several questions somewhat clearly marked. Answering the questions in the code below is what I am looking for.
.file "main.cpp"
.section .text._ZN3topC2Ev,"axG",@progbits,_ZN3topC5Ev,comdat
.align 2
.weak _ZN3topC2Ev
.type _ZN3topC2Ev, @function
_ZN3topC2Ev:
.LFB3:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; LFB3 Associated with the address for the class top constructor
.cfi_def_cfa_offset 16 ;
.cfi_offset 6, -16 ; %rdi, -8(%rbp) pushes 8 bytes (64 bits for t).
movq %rsp, %rbp ; onto the stack
.cfi_def_cfa_register 6
movq %rdi, -8(%rbp)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE3:
.size _ZN3topC2Ev, .-_ZN3topC2Ev
.weak _ZN3topC1Ev
.set _ZN3topC1Ev,_ZN3topC2Ev
.section .text._ZN4leftC2Ev,"axG",@progbits,_ZN4leftC2Ev,comdat
.align 2
.weak _ZN4leftC2Ev
.type _ZN4leftC2Ev, @function
_ZN4leftC2Ev:
.LFB6:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; LFB6 Associated with the adress for the class left constructor
.cfi_def_cfa_offset 16 ;
.cfi_offset 6, -16 ; %rdi, -8(%rbp) pushes 8 bytes (64 bits for l).
movq %rsp, %rbp ; onto the stack
.cfi_def_cfa_register 6 ;
movq %rdi, -8(%rbp) ; %rdi, -16(%rbp) pushes 8 more bytes (64 bits for t).
movq %rsi, -16(%rbp) ;
movq -16(%rbp), %rax ; What does the rest of this do?
movq (%rax), %rdx
movq -8(%rbp), %rax
movq %rdx, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size _ZN4leftC2Ev, .-_ZN4leftC2Ev
.section .text._ZN5rightC2Ev,"axG",@progbits,_ZN5rightC2Ev,comdat
.align 2
.weak _ZN5rightC2Ev
.type _ZN5rightC2Ev, @function
_ZN5rightC2Ev:
.LFB9:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; LFB9 Associated with the adress for the class left constructor
.cfi_def_cfa_offset 16 ;
.cfi_offset 6, -16 ; %rdi, -8(%rbp) pushes 8 bytes (64 bits for r).
movq %rsp, %rbp ; onto the stack
.cfi_def_cfa_register 6 ;
movq %rdi, -8(%rbp) ; %rdi, -16(%rbp) pushes 8 more bytes (64 bits for t).
movq %rsi, -16(%rbp) ;
movq -16(%rbp), %rax ; What does the rest of this do?
movq (%rax), %rdx
movq -8(%rbp), %rax
movq %rdx, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE9:
.size _ZN5rightC2Ev, .-_ZN5rightC2Ev
.section .text._ZN6bottomC1Ev,"axG",@progbits,_ZN6bottomC1Ev,comdat
.align 2
.weak _ZN6bottomC1Ev
.type _ZN6bottomC1Ev, @function
_ZN6bottomC1Ev:
.LFB12:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; LFB12 Associated with the adress for the class left constructor
.cfi_def_cfa_offset 16 ;
.cfi_offset 6, -16 ; %rdi, -8(%rbp) pushes 8 bytes (64 bits for b).
movq %rsp, %rbp ; onto the stack
.cfi_def_cfa_register 6 ;
subq $16, %rsp ; Construct all the base objects placing t into b only once?
movq %rdi, -8(%rbp) ;
movq -8(%rbp), %rax
addq $32, %rax
movq %rax, %rdi
call _ZN3topC2Ev
movl $_ZTT6bottom+8, %edx
movq -8(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call _ZN4leftC2Ev
movl $_ZTT6bottom+16, %eax
movq -8(%rbp), %rdx
addq $16, %rdx
movq %rax, %rsi
movq %rdx, %rdi
call _ZN5rightC2Ev
movl $_ZTV6bottom+24, %edx
movq -8(%rbp), %rax
movq %rdx, (%rax)
movl $_ZTV6bottom+48, %edx
movq -8(%rbp), %rax
movq %rdx, 16(%rax)
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE12:
.size _ZN6bottomC1Ev, .-_ZN6bottomC1Ev
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; Store off the base pointer
.cfi_def_cfa_offset 16 ; Debug code to trace where stack pointer is?
.cfi_offset 6, -16 ;
movq %rsp, %rbp ; Move where stack pointer is to base pointer
.cfi_def_cfa_register 6 ;
pushq %rbx ; Store off what might have been in rbx
subq $24, %rsp ; Push argc onto stack
.cfi_offset 3, -24 ;
movl $40, %edi ; Push argv onto stack
call _Znwm ; Call new
movq %rax, %rbx ; Create room for b %rax contains address of memory
movq $0, (%rbx) ; location where new returned?
movq $0, 8(%rbx) ;
movq $0, 16(%rbx) ;
movq $0, 24(%rbx) ;
movq $0, 32(%rbx) ;
movq %rbx, %rdi ; Move that data into dynamic memory?
call _ZN6bottomC1Ev ; Call the construtor of the object
movq %rbx, -32(%rbp) ;
movq -32(%rbp), %rax ; Can someone explain how this code relates to
movl $5, 8(%rax) ; the explaination at:
movq -32(%rbp), %rax ; http://www.phpcompiler.org/articles/virtualinheritance.html?
movq %rax, -24(%rbp)
movq -24(%rbp), %rax
movl 8(%rax), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
addq $24, %rsp
popq %rbx
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.weak _ZTV6bottom
.section .rodata._ZTV6bottom,"aG",@progbits,_ZTV6bottom,comdat
.align 32
.type _ZTV6bottom, @object
.size _ZTV6bottom, 48
_ZTV6bottom:
.quad 32
.quad 0
.quad _ZTI6bottom
.quad 16
.quad -16
.quad _ZTI6bottom
.weak _ZTT6bottom
.section .rodata._ZTT6bottom,"aG",@progbits,_ZTV6bottom,comdat
.align 32
.type _ZTT6bottom, @object
.size _ZTT6bottom, 32
_ZTT6bottom:
.quad _ZTV6bottom+24
.quad _ZTC6bottom0_4left+24
.quad _ZTC6bottom16_5right+24
.quad _ZTV6bottom+48
.weak _ZTC6bottom0_4left
.section .rodata._ZTC6bottom0_4left,"aG",@progbits,_ZTV6bottom,comdat
.align 16
.type _ZTC6bottom0_4left, @object
.size _ZTC6bottom0_4left, 24
_ZTC6bottom0_4left:
.quad 32
.quad 0
.quad _ZTI4left
.weak _ZTC6bottom16_5right
.section .rodata._ZTC6bottom16_5right,"aG",@progbits,_ZTV6bottom,comdat
.align 16
.type _ZTC6bottom16_5right, @object
.size _ZTC6bottom16_5right, 24
_ZTC6bottom16_5right:
.quad 16
.quad 0
.quad _ZTI5right
.weak _ZTS6bottom
.section .rodata._ZTS6bottom,"aG",@progbits,_ZTS6bottom,comdat
.type _ZTS6bottom, @object
.size _ZTS6bottom, 8
_ZTS6bottom:
.string "6bottom"
.weak _ZTI6bottom
.section .rodata._ZTI6bottom,"aG",@progbits,_ZTI6bottom,comdat
.align 32
.type _ZTI6bottom, @object
.size _ZTI6bottom, 56
_ZTI6bottom:
.quad _ZTVN10__cxxabiv121__vmi_class_type_infoE+16
.quad _ZTS6bottom
.long 2
.long 2
.quad _ZTI4left
.quad 2
.quad _ZTI5right
.quad 4098
.weak _ZTS5right
.section .rodata._ZTS5right,"aG",@progbits,_ZTS5right,comdat
.type _ZTS5right, @object
.size _ZTS5right, 7
_ZTS5right:
.string "5right"
.weak _ZTI5right
.section .rodata._ZTI5right,"aG",@progbits,_ZTI5right,comdat
.align 32
.type _ZTI5right, @object
.size _ZTI5right, 40
_ZTI5right:
.quad _ZTVN10__cxxabiv121__vmi_class_type_infoE+16
.quad _ZTS5right
.long 0
.long 1
.quad _ZTI3top
.quad -6141
.weak _ZTS4left
.section .rodata._ZTS4left,"aG",@progbits,_ZTS4left,comdat
.type _ZTS4left, @object
.size _ZTS4left, 6
_ZTS4left:
.string "4left"
.weak _ZTI4left
.section .rodata._ZTI4left,"aG",@progbits,_ZTI4left,comdat
.align 32
.type _ZTI4left, @object
.size _ZTI4left, 40
_ZTI4left:
.quad _ZTVN10__cxxabiv121__vmi_class_type_infoE+16
.quad _ZTS4left
.long 0
.long 1
.quad _ZTI3top
.quad -6141
.weak _ZTS3top
.section .rodata._ZTS3top,"aG",@progbits,_ZTS3top,comdat
.type _ZTS3top, @object
.size _ZTS3top, 5
_ZTS3top:
.string "3top"
.weak _ZTI3top
.section .rodata._ZTI3top,"aG",@progbits,_ZTI3top,comdat
.align 16
.type _ZTI3top, @object
.size _ZTI3top, 16
_ZTI3top:
.quad _ZTVN10__cxxabiv117__class_type_infoE+16
.quad _ZTS3top
.ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
.section .note.GNU-stack,"",@progbits
_ZN4leftC2Ev:
.LFB6:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; LFB6 Associated with the adress for the class left constructor
.cfi_def_cfa_offset 16 ;
.cfi_offset 6, -16 ; %rdi, -8(%rbp) - his doesn't initialize anything - it is just pushing this value on local stack, in optimized version it will probably dissapear.
movq %rsp, %rbp ; %rsi, -16(%rbp) - just as above;
.cfi_def_cfa_register 6 ; %rsi - pointer to virtual table table (not a mistake it's vtt, not vt) for left-in-bottom
movq %rdi, -8(%rbp) ; %rdi - pointer to left instance
movq %rsi, -16(%rbp) ;
movq -16(%rbp), %rax ; What does the rest of this do?
movq (%rax), %rdx ; It is just copying vtable address form vtt to first eight bytes of actual object.
movq -8(%rbp), %rax
movq %rdx, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
This is an unoptimized code so compiler does lots of unnecessary stuff like putting values from registers to memory and then back again into registers, but if you follow values around, you will notice there isn't really that much happening. Code gets pointer to storage for object and pointer to vtt entry, it is just deferencing vtt entry and putting found vtable pointer to first 8 bytes of object storage. Vtt being used is actually just a temporary one, used for sub-object construction, as the final one is added by bottom
constructor:
movl $_ZTV6bottom+24, %edx
movq -8(%rbp), %rax
movq %rdx, (%rax)
movl $_ZTV6bottom+48, %edx
movq -8(%rbp), %rax
movq %rdx, 16(%rax)
As for main
:
main:
.LFB0:
.cfi_startproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
pushq %rbp ; Store off the base pointer
.cfi_def_cfa_offset 16 ; Debug code to trace where stack pointer is?
.cfi_offset 6, -16 ;
movq %rsp, %rbp ; Move where stack pointer is to base pointer
.cfi_def_cfa_register 6 ;
pushq %rbx ; Store off what might have been in rbx
subq $24, %rsp ; Push argc onto stack
.cfi_offset 3, -24 ;
movl $40, %edi ; Push argv onto stack
call _Znwm ; Call new
movq %rax, %rbx ; Create room for b %rax contains address of memory
movq $0, (%rbx) ; location where new returned?
movq $0, 8(%rbx) ; A: Yes - eax contains address of returned buffer
movq $0, 16(%rbx) ; It is being zeroed to 5*8 = 48 bytes
movq $0, 24(%rbx) ;
movq $0, 32(%rbx) ;
movq %rbx, %rdi ; Move that data into dynamic memory? -hmmm, what? Just moving pointr to it into rdi, where bottom constructor expects it
; it is still where it was - in dynamic memory from new (_Znwm), jut it's pointer changed register ;)
call _ZN6bottomC1Ev ; Call the construtor of the object
movq %rbx, -32(%rbp) ;
movq -32(%rbp), %rax ; Here isn't happening much - classes bottom and left are sure to start at the same address, so compiler doesn't need to chack for anything,
movl $5, 8(%rax) ; just use offset to addres fields, and copy the pointer without modification to do the castting from bottom to left.
movq -32(%rbp), %rax ;
movq %rax, -24(%rbp)
movq -24(%rbp), %rax
movl 8(%rax), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
addq $24, %rsp
popq %rbx
popq %rbp
.cfi_def_cfa 7, 8
ret
As for linked article, I am unsure what part do you mean. Of what exactly you are unsure? Your code actually doesn't do anything with fields in your classes, and they don't appear in assembly - code is just handling vmt pointers around. What exactly is what you do not understand?
One thing worth noticing is that even though virtual-offsets for sub-class instance are in the vtable, everywhere where object's full type is known those values can be simply hardcoded; same goes for virtual method call and anything that touches vtable for that matter. I am surprised though that unoptimized version still uses hardcoded values.