(八)函数调用为何会发生“Stack Overflow”
一、一次函数调用分析
c代码:
// function_example.c
#include <stdio.h>
int static add(int a, int b)
{
return a+b;
} int main()
{
int x = 5;
int y = 10;
int u = add(x, y);
}
编译并objdump:
$ gcc -g -c function_example.c
$ objdump -d -M intel -S function_example.o
int static add(int a, int b)
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
return a+b;
a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
10: 01 d0 add eax,edx
}
12: 5d pop rbp
13: c3 ret
0000000000000014 <main>:
int main()
{
14: 55 push rbp
15: 48 89 e5 mov rbp,rsp
18: 48 83 ec 10 sub rsp,0x10
int x = 5;
1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5
int y = 10;
23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa
int u = add(x, y);
2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
30: 89 d6 mov esi,edx
32: 89 c7 mov edi,eax
34: e8 c7 ff ff ff call 0 <add>
39: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
3c: b8 00 00 00 00 mov eax,0x0
}
41: c9 leave
42: c3 ret
分析:
- 可以看到main方法和add方法的入口处14行和0行都有一个push操作,紧接着是一个mov操作将rsp赋值给rbp,rbp正是上一步push的参数;
- 同时可以看到在add方法的结尾有一个pop指令,参数正是函数开头push的参数rbp;
- add函数和main函数结尾都有一个ret指令;
- main方法中1c行到32行
- 首先对两个内存地址分别赋值5和10
- 将刚刚赋值过的两个内存地址的值再次赋值给edx和eda
- 将dex和eda的值分别赋给esi和edi
- main函数34行执行了call指令,参数是add函数;
实际上PUSH指令就表示压栈,POP就表示出栈。其实上述的调用过程和if/else的指令都进行了指令地址跳转,区别在于通过if/else跳转后,指令顺序执行到下一行,而call命令执行的跳转函数完成后还会调回call命令下一行,这是如何实现的呢?
程序栈说明:
我们自己思考这个功能该如何实现呢?
第一种方法,我们可以将add方法的指令直接拼接到main方法指令行的对应位置,这个方法的问题是,假如main方法和add相互调用(循环调用)那么不断拼接无穷无尽,显然不行。
第二种方法,我们可以单独设置一个程序执行寄存器,每次函数执行都记录该函数执行需要返回的位置,这样等该函数执行完成,直接返回到记录的返回位置即可,但是在多级函数调用情况下,只记录一个跳转机制远远不够,因此计算机科学家实际使用了一种更好的办法;
实际栈结构:

- 在内存中开辟一块区域保存先进后出的栈结构来记录函数调用的返回地址
- A函数调用B函数时,在进入B函数后,首先将A函数调用B函数的下一行指令地址放入栈中,此之谓压栈,假若B函数又调用了C函数,就又在C函数一开始将B调用C的下一行指令地址压入栈顶
- C函数执行完成,从栈顶拿出要返回的地址并将指令跳转回该地址,此之谓出栈,随之B函数执行完成,此时又从栈中取出栈顶存放的指令地址并跳转执行;
- 如此,调用层级越深的函数所记录的跳转地址越接近于栈顶,如此弹栈过程遍类似于反向遍历;
汇编代码的表现:
- add方法0行,rbp表示的是当前栈的指针,此时push rbp表示将main函数栈帧的栈底地址压入了程序栈顶;
- 紧接着1行,mov rbp,rsp 将rsp的值赋给rbp,而rsp是一个始终指向栈顶的参数,此时rbp指向栈顶;
- add末尾pop rbp表示弹栈,将当前的栈顶出栈,由此维护好了栈;
- 由于call调用时PC寄存器将返回地址压栈,最后执行ret指令时将call指令压入栈顶的返回地址弹栈并替换到PC寄存器中,以此完成指令跳转。
二、构造一次Stack Overflow
由于栈的大小有限,因此当调用层级太多时,会由于压栈造成栈空间不足以致溢出,典型场景为不加限制的递归调用:
int a()
{
return a();
} int main()
{
a();
return 0;
}
三、使用函数内联进行性能优化
程序编译时将实际函数调用产生的指令直接插入到调用位置,来替换对应的函数调用指令,称为函数内联。
这样做的好处是:减少了指令数、减少了函数调用时的压栈弹栈开销;
但是当目标函数被引用很多次时会多次展开,使得程序占用空间变得很大。
(八)函数调用为何会发生“Stack Overflow”的更多相关文章
- 重学计算机组成原理(六)- 函数调用怎么突然Stack Overflow了!
用Google搜异常信息,肯定都访问过Stack Overflow网站 全球最大的程序员问答网站,名字来自于一个常见的报错,就是栈溢出(stack overflow) 从函数调用开始,在计算机指令层面 ...
- 函数调用堆栈及活动记录 堆栈溢出 stack overflow
小结: 1.当被调函数返回主调函数时,被调函数的 活动记录-activation record / 堆栈帧-stack frame 被 弹出-popping 程序执行栈-program executi ...
- Stack Overflow上59万浏览量的提问:为什么会发生ArrayIndexOutOfBoundsException?
在逛 Stack Overflow 的时候,发现了一些访问量像昆仑山一样高的问题,比如说这个:为什么会发生 ArrayIndexOutOfBoundsException?这样看似简单到不值得一问的问题 ...
- Stack的三种含义(数据超过栈的大小,就发生stack overflow)
非常典型的基础知识,转自http://www.ruanyifeng.com/blog/2013/11/stack.html 学习编程的时候,经常会看到stack这个词,它的中文名字叫做"栈& ...
- [转] log4j-over-slf4j与slf4j-log4j12共存stack overflow异常分析
[From] http://www.tuicool.com/articles/INveIf 注:下文中的“桥接”.“转调”.“绑定”等词基本都是同一个概念. log4j-over-slf4j和slf4 ...
- Stack overflow 编译能通过,运行时出现Stack overflow
Stack overflow 编译能通过,运行时出现Stack overflow 大家都知道,Windows程序的内存机制大概是这样的,全局变量(局部的静态变量本质也属于此范围)存储于堆内存,该段内存 ...
- Stack Overflow是如何做应用缓存的
首先要说下缓存是什么?缓存,就是在取出数据结果后,暂时将数据存储在某些可以快速存取的位置(例如各种NoSQL如Redis,HBase,又或MemoryCache等等),于是就可以让这些耗时的数据结果多 ...
- Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder
Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder 转自:ht ...
- Stack Overflow: The Architecture - 2016 Edition(Translation)
原文: https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/ 作者:Nick Cra ...
随机推荐
- 实用fork/join框架提升程序效率
实用fork/join框架提成程序效率 原文地址:https://www.jianshu.com/p/9ce243796d4a 业务场景 最近再做一个接口,我是一个中央的消息接受方,当我接受到消息后要 ...
- php 导出excel 10万数据
php导出excel 10万数据(此代码主要测试用) 在工作当中要对一些基本信息和其他信息导出 起初信息比较小无所谓.... 但当信息超出65535的时候 发现点问题了 超出了 而且 反应速度很慢 实 ...
- Core WebApi项目快速入门(一):环境部署
1.WebApi新建与部署 1.1 新建Core WebApi工程 1.2 部署 1.2.1 IIS部署 首先以文件方式发布应用程序,然后下载依赖.net core运行时及host安装包 在iis中看 ...
- laravel 控制器中获取不到session
protected $middleware = [ \Illuminate\Session\Middleware\StartSession::class, ]; 在 kernel.php中 加入Sta ...
- 预训练模型——开创NLP新纪元
预训练模型--开创NLP新纪元 论文地址 BERT相关论文列表 清华整理-预训练语言模型 awesome-bert-nlp BERT Lang Street huggingface models 论文 ...
- tcp ESTABLISHED 接收数据
tcp_rcv_established函数的工作原理是把数据包的处理分为2类:fast path和slow path,其含义显而易见.这样分类的目的当然是加快数据包的处理,因为在正常情况下,数据包是按 ...
- 再聊 Blazor,它是否值得你花时间学习
之前写了一篇文章<快速了解 ASP.NET Core Blazor>,大家关心最多的问题是,我该不该花时间去学习 Blazor.今天聊聊这个话题,并表达一下我个人的看法. 在此之前,我还是 ...
- SpringBoot第十一集:整合Swagger3.0与RESTful接口整合返回值(2020最新最易懂)
SpringBoot第十一集:整合Swagger3.0与RESTful接口整合返回值(2020最新最易懂) 一,整合Swagger3.0 随着Spring Boot.Spring Cloud等微服务的 ...
- spring cloud 入门系列
springcloud入门总结转发自:https://www.cnblogs.com/sam-uncle/p/9340390.html 最近看到微服务很火,也是未来的趋势, 所以就去学习下,在dubb ...
- Python_案例_斐波那契数
方法一: 1 #!/usr/bin/python3 2 3 # Fibonacci series: 斐波纳契数列 4 # 两个元素的总和确定了下一个数 5 a, b = 0, 1 6 while b ...