I started learning x86 assembly today by analyzing the assembly code corresponding to this c++ example (I know there exists something like atoi
but I wanted to keep the example minimal):
#include <vector>
std::vector<int> range(int N) {
std::vector<int> v(N);
for (unsigned int i = 0; i < N; ++i)
v[i] = i;
return v;
}
int main() {
return range(100).back();
}
If compiled with g++ -O0 -S -fno-stack-protector return_by_value.cpp
, this results in this excerpt:
... <snip>
_Z5rangei:
.LFB509:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA509
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $40, %rsp
.cfi_offset 3, -24
movq %rdi, -40(%rbp)
movl %esi, -44(%rbp)
leaq -25(%rbp), %rax
movq %rax, %rdi
call _ZNSaIiEC1Ev
movl $0, -24(%rbp)
movl -44(%rbp), %eax
movslq %eax, %rsi
leaq -25(%rbp), %rcx
leaq -24(%rbp), %rdx
... <snip>
I was surprised to see an odd (i.e. not multiple of 8) offset: leaq -25(%rbp), %rax
, especially since it is a q
instruction and further we have also -24(%rbp)
. For what reason is the compiler reading across 8 byte boundaries?
Looking at this fragment:
leaq -25(%rbp), %rax
movq %rax, %rdi
call _ZNSaIiEC1Ev
_ZNSaIiEC1Ev
is demangled to std::allocator<int>::allocator()
, so -25(%rbp)
is the address of an allocator<int>
object which is passed to the constructor. If we print the sizeof
of this object in GCC we will get 1. Since the size of the object is 1, there is no need to align it to 8 bytes and it can be placed in any memory address.
The -24(%rbp)
which you see later is the address of a different object, and the compiler is not reading across an 8-byte boundary.
Note that the lea
instruction does not actually access memory - it only calculates an address. Therefore, the fact that it has a q
suffix does not mean that it accesses 8 bytes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With