If I compile the following with -O3
#include <vector>
int foo() {
std::vector<int> const v{17,2,3};
return v[0] + v[2];
}
the assembly I get is
foo():
mov eax, 20
ret
And I understand how that would work when called by some other TU.
But if I compile with -O0
, I get this:
_Z3foov:
pushq %rbp
movq %rsp, %rbp
subq $112, %rsp
movl $17, -84(%rbp)
movl $2, -80(%rbp)
movl $3, -76(%rbp)
leaq -84(%rbp), %rax
movq %rax, -72(%rbp)
movq $3, -64(%rbp)
leaq -85(%rbp), %rcx
movq %rcx, -32(%rbp)
movq -32(%rbp), %rax
movq %rax, -8(%rbp)
movq -72(%rbp), %rsi
movq -64(%rbp), %rdx
leaq -56(%rbp), %rdi
callq _ZNSt6vectorIiSaIiEEC2ESt16initializer_listIiERKS0_
jmp .LBB0_1
.LBB0_1:
leaq -85(%rbp), %rax
movq %rax, -24(%rbp)
leaq -56(%rbp), %rdi
xorl %eax, %eax
movl %eax, %esi
callq _ZNKSt6vectorIiSaIiEEixEm
movl (%rax), %eax
movl %eax, -108(%rbp)
leaq -56(%rbp), %rdi
movl $2, %esi
callq _ZNKSt6vectorIiSaIiEEixEm
movq %rax, %rcx
movl -108(%rbp), %eax
addl (%rcx), %eax
movl %eax, -104(%rbp)
leaq -56(%rbp), %rdi
callq _ZNSt6vectorIiSaIiEED2Ev
movl -104(%rbp), %eax
addq $112, %rsp
popq %rbp
retq
movq %rax, %rcx
movl %edx, %eax
movq %rcx, -96(%rbp)
movl %eax, -100(%rbp)
leaq -85(%rbp), %rax
movq %rax, -16(%rbp)
movq -96(%rbp), %rdi
callq _Unwind_Resume@PLT
__clang_call_terminate:
pushq %rbp
movq %rsp, %rbp
callq __cxa_begin_catch@PLT
callq _ZSt9terminatev@PLT
.L.str:
.asciz "cannot create std::vector larger than max_size()"
.L.str.1:
.asciz "/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/bits/stl_vector.h"
.L__PRETTY_FUNCTION__._ZNKSt6vectorIiSaIiEEixEm:
.asciz "const_reference std::vector<int>::operator[](size_type) const [_Tp = int, _Alloc = std::allocator<int>]"
.L.str.2:
.asciz "__n < this->size()"
DW.ref.__gxx_personality_v0:
.quad __gxx_personality_v0
where _ZNKSt6vectorIiSaIiEEixEm
is the called when computing v[0]
and v[2]
. But I don't really understand where those 2 call
s jump. I mean, the string _ZNKSt6vectorIiSaIiEEixEm
appears only 3 times, 2 are the calls
, and one is this:
.L__PRETTY_FUNCTION__._ZNKSt6vectorIiSaIiEEixEm:
.asciz "const_reference std::vector<int>::operator[](size_type) const [_Tp = int, _Alloc = std::allocator<int>]"
How can this possibly result in pulling the elemnts out of the vector?
Or, in other words, if I link the above TU with another one that calls it and returns the value, e.g.
int foo();
int main() {
return foo();
}
where do I find the code the code that executes v[0]
and v[2]
? If it's not in the assembly, it's not even in the binary, right? Then how does the program run?
Filter: Library Functions is enabled by default in the Godbolt compiler explorer.
Disabling that in the Filter drop-down menu, there's a definition for _ZNKSt6vectorIiSaIiEEixEm
.
(std::vector<int, std::allocator<int>>::operator[](unsigned long) const:
with name-demangling enabled.)
(Its definition includes bounds-checking and an assert-fail that prints the function name, hence the .L__PRETTY_FUNCTION__._ZNKSt6vectorIiSaIiEEixEm:
definition which the library-function filter didn't discard).