进程的虚拟内存中的标准内存段布局
4G 0xffff ffff kernel space, user code write or read segmentation fault
3G 0xbfff ffff random stack offset
rlimit_stack, 8M
random mmap offset
memory mapping segment, libc
heap
bss
data
text
0 reserved
Random stack offset和Random mmap offset等随机值意在防止恶意程序
Linux通过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以免恶意程序通过计算访问栈、库函数等地址
堆和栈都是匿名页,堆向上,栈向下
函数调用,通过汇编查看堆栈过程
源码:
int bar(int c, int d)
{
int e = c + d;
return e;
}
int foo(int a, int b)
{
return bar(a, b);
}
int main(void)
{
foo(2, 5);
return 0;
}
汇编:objdump -Sd
0000000000400506 <main>:
int main(void)
{ main 被 _start调用
400506: 55 push %rbp 栈底地址入栈,栈开始
400507: 48 89 e5 mov %rsp,%rbp 建立main函数的栈帧,帧开始bp为_start的sp
foo(2, 5);
40050a: be 05 00 00 00 mov $0x5,%esi si是存储第二个变量寄存器
40050f: bf 02 00 00 00 mov $0x2,%edi di是存储第一个变量寄存器
400514: e8 ce ff ff ff callq 4004e7 <foo> call函数foo,相当于
pushq %rip ip=400519入栈,sp-8
jmpq addr ip执行4004e7
return 0;
400519: b8 00 00 00 00 mov $0x0,%eax
}
40051e: 5d pop %rbp 栈开始地址回到_start
40051f: c3 retq popq %rip,ip出栈执行400519
00000000004004e7 <foo>:
int foo(int a, int b)
{
4004e7: 55 push %rbp
4004e8: 48 89 e5 mov %rsp,%rbp
4004eb: 48 83 ec 08 sub $0x8,%rsp 建立foo的栈帧,向下扩展sp
4004ef: 89 7d fc mov %edi,-0x4(%rbp) 传参1入栈
4004f2: 89 75 f8 mov %esi,-0x8(%rbp) 传参2入栈
return bar(a, b);
4004f5: 8b 55 f8 mov -0x8(%rbp),%edx 参数1此处无计算,直接存储
4004f8: 8b 45 fc mov -0x4(%rbp),%eax 参数2此处无计算,直接存储
4004fb: 89 d6 mov %edx,%esi si是存储第二个变量寄存器
4004fd: 89 c7 mov %eax,%edi di是存储第一个变量寄存器
4004ff: e8 c9 ff ff ff callq 4004cd <bar> pushq %rip ip 400504入栈,sp-8
jmpq addr ip执行4004cd
}
400504: c9 leaveq 与函数进入对应,恢复上级的栈地址
movq %rbp, %rsp 栈开始为上级结束
popq %rbp 出栈为上级开始
400505: c3 retq popq %rip,ip出栈执行
int bar(int c, int d)
{
4004cd: 55 push %rbp
4004ce: 48 89 e5 mov %rsp,%rbp 不扩建栈帧,最后一个函数不必要保存局部变量
4004d1: 89 7d ec mov %edi,-0x14(%rbp) 直接获取上层的入参,这里变量e是栈前4字节
4004d4: 89 75 e8 mov %esi,-0x18(%rbp)
int e = c + d;
4004d7: 8b 45 e8 mov -0x18(%rbp),%eax
4004da: 8b 55 ec mov -0x14(%rbp),%edx
4004dd: 01 d0 add %edx,%eax
4004df: 89 45 fc mov %eax,-0x4(%rbp)
return e;
4004e2: 8b 45 fc mov -0x4(%rbp),%eax
}
4004e5: 5d pop %rbp
4004e6: c3 retq
(gdb) info registers rbp rsp
rbp 0x7fffffffe478 0x7fffffffe478
rsp 0x7fffffffe478 0x7fffffffe478
(gdb) x/16x
0x7fffffffe478: 0xffffe490 0x00007fff 0x00400504 0x00000000
0x7fffffffe488: 0x00000005 0x00000002 0xffffe4a0 0x00007fff
0x7fffffffe498: 0x00400519 0x00000000 0x00000000 0x00000000
0x7fffffffe4a8: 0xf7a30445 0x00007fff 0x00000000 0x00000000
当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的
尾递归,汇编将没有call,而是直接跳转
控制栈的增长,且减少压栈,程序运行的效率也可能更高