STACK UNWINDING - 2013

本文详细介绍了C++中函数调用栈的工作原理,包括如何使用栈来管理函数调用、返回地址及局部变量,并解释了递归与异常处理过程中栈的作用。
CALL STACK

Stacks are last-in, first-out (LIFO) data structures, in other words, the last item pushed (inserted) on the stack is the first item popped (removed) from it. To understand how C++ performs function calls, we should know it's using a stack data structure.

 

The function call stack supports the function call/return mechanism. It also supports the creation, maintenance and destruction of each called function's automatic variables. This LIFO behavior is exactly what a function does when returning to the function that called it.

Since the call stack is organized as a stack, the caller pushes the return address onto the stack, and the called function, when it finishes, pops the return address off the call stack and transfers control to that address. If a called function calls on to yet another function, it will push another return address onto the call stack, and so on, with the information stacking up and unstacking as the program dictates.





So, we must keep track of the return addresses that each function needs to return control to its caller. The function call stack is the perfect data structure for handling this information. Each time a function is called, an entry is pushed onto the stack. This entry, called a stack frameactivation frame or an activation record, contains the return address that the called function needs to return to the calling function. When the called function returns, the stack frame for the function call is popped, and control transfers to the return address in the popped stack frame.

Each called function always finds the information it needs to return to its caller at the top of the call stack. And, if a function makes a call to another function, a stack frame for the new function call is simply pushed onto the call stack. Thus, the return address required by the newly called function to return to its caller is now located at the top of the stack.

Using a stack to save the return address has important advantages over alternatives. One is that each task has its own stack, and thus the function can be reentrant, that is, can be active simultaneously for different tasks doing different things. Another benefit is that recursion is automatically supported. When a function calls itself recursively, a return address needs to be stored for each activation of the function so that it can later be used to return from the function activation. This capability is automatic with a stack.

The stack frames have another important responsibility. Most functions have automatic (local) variables. These variables that are known only within the active function and do not retain values after it returns. It is often convenient to allocate space for this use by simply moving the top of the stack by enough to provide the space. This is very fast compared to heap allocation. Note that each separate activation of a function gets its own separate space in the stack for locals.

The called function's stack frame is a perfect place to store the function's automatic variables. That stack frame exists as long as the called function is active. When that function returns, and no longer needs its local automatic variables, its stack frame is popped from the stack, and those local automatic variables are no longer there.



call stack  

To call a function f(x)...

  1. Evaluate the actual parameter expressions, such as the x, in the caller's context.
  2. Allocate memory for f()'s locals by pushing a suitable local block of memory onto a runtime call stack dedicated to this purpose. For parameters but not local variables, store the values from step (1) into the appropriate slot in f()'s local block.
  3. Store the caller's current address of execution (its return address) and switch execution to f().
  4. f() executes with its local block conveniently available at the end of the call stack.
  5. When f() is finished, it exits by popping its locals off the stack and returns to the caller using the previously stored return address. Now the caller's locals are on the end of the stack and it can resume executing.

As an addition note for the function call process:

  • This is why infinite recursion results in a Stack Overflow Error - the code keeps calling and calling resulting in steps (1) (2) (3), (1) (2) (3), but never a step (4)....eventually the call stack runs out of memory.
  • This is why local variables have random initial values - step (2) just pushes the whole local block in one operation. Each local gets its own area of memory, but the memory will contain whatever the most recent tenant left there. To clear all of the local block for each function call would be too time expensive.
  • The local block is also known as the function's activation record or stack frame. The entire block can be pushed onto the stack (step 2), in a single CPU operation - it is a very fast operation.
  • For a multithreaded environment, each thread gets its own call stack instead of just having single, global call stack.
  • For performance reasons, some languages pass some parameters through registers and others through the stack, so the overall process is complex. However, the apparent the lifetime of the variables will always follow the stack model presented here.


The amount of memory is finite, so only a certain amount of memory can be used to store activation records on the function call stack. If more function calls occur than can have their activation records stored on the function call stack, an error known as stack overflow occurs. In other words, if the pushing consumes all of the space allocated for the call stack, an error called a stack overflow occurs, generally causing the program to crash.

Adding a subroutine's entry to the call stack is sometimes called winding. Conversely, removing entries is called unwinding.

STACK UNWINDING

call stack is a stack data structure that stores information about the active functions. The call stack is also known as an execution stack, control stack, function stack, or run-time stack. The main reason for having a call stack is to keep track of the point to which each active function should return control when it completes executing. Here, the active functions are those which have been called but have not yet completed execution by returning.

Before we look into the exception aspect of the call stack, let's look at how C++ normally handles function calls and returns. C++ usually handles function calls by placing information on a stack, actually what it is placing is the address of a calling function instruction with its return address. When the called function completes, the program uses that address to decide where to continue the execution. Besides the return address, the function call puts any function arguments on the stack. They are treated as automatic variables. If the called function creates any additional automatic variables, they, too, are added to the stack.

When a function terminates, execution goes to the address stored when the function was called, and the stack for the called function is freed. So, a function normally returns to the function that called it, with each function freeing its automatic variables as it completes. If an automatic variable is a class object, then the destructor for that class is called.



Stack Unwinding  

Here is the example showing the normal flow of execution.

#include <iostream>
#include <string>

using namespace std;

class MyClass {
private:
	string name;
public:
	MyClass (string s) :name(s) {}
	~MyClass() {
		cout << "Destroying " << name << endl;
	}
};

void fa();
void fb();
void fc();
void fd();

int main( ) 
{ 
	try {
		MyClass mainObj("M");
		fa();
		cout << "Mission accomplished!\n";
	}
	catch (const char *e) {
		cout << "exception: " << e << endl;
		cout << "Mission impossible!\n";
	}	
	return 0; 
}

void fa() {
	MyClass a("A");
	fb();
	cout << "return from fa()\n";
	return;
}

void fb() {
	MyClass b("B");
	fc();
	cout << "return from fb()\n";
	return;
}

void fc() {
	MyClass c("C");
	fd();
	cout << "return from fc()\n";
	return;

}

void fd() {
	MyClass d("D");
	// throw "in fd(), something weird happened.";
	cout << "return from fd()\n";
	return;
}

Out from the run is:

return from fd()
Destroying D
return from fc()
Destroying C
return from fb()
Destroying B
return from fa()
Destroying A
Mission accomplished!
Destroying M

Here, we see two things:

  1. Unwinding stacks from the top. Top means the latest call.
  2. Destructor for each object in each function is called when the function completes.

Now it's time to learn something related to the exception. Let's start with throw() in the last function and compare the result with the normal execution flow. So, off the comment from the line in the last function fd().

// throw "in fd(), something weird happened."; =>
throw "in fd(), something weird happened.";

If we run this new code again, we get the following output:

Destroying D
Destroying C
Destroying B
Destroying A
Destroying M
exception: in fd(), something weird happened.
Mission impossible!

This time, we have an exception thrown from the function d(). The catch block caught that exception. The main difference compared with the previous run is we don't have the output like:

return from fd()
....
return from fa()

What happened? 
This time, each function terminated due to a thrown exception instead of normal return call. Still, the program frees memory from the stack. However, instead of stopping at the first return address on the stack, it continues freeing the stack until it reaches a return address that resides in a try block. Execution control then passes to the exception handlers at the end of the try block rather than to the first statement following the function call:

cout << "Mission accomplished!\n";
This process is the  unwinding the stack .

Let's use simpler example:

#include <iostream>
#include <string>

 void b()
 { 
  throw std::exception();
 }
 
 void a()
 {
   std::string str = "Oops"; 
   b();
 }
 
 int main()
 {
   try
   {
      a();
   } 
   catch(...) 
   { }
 }

The following things happen during the stack unwinding process:

  1. main() calls a().
  2. a() creates a local variable named str.
  3. str constructor allocates a memory chunk to hold the string "Oops"
  4. a() calls b().
  5. b() throws an exception.
  6. Because a() does not catch the exception, we now need to exit a() in a clean fashion.
  7. At this point, all the destructors of local variables previous to the throw are called - This is called stack unwinding.
  8. The destructor of str is called, which releases the memory occupied by it.
  9. The mechanism of stack unwinding is essential to prevent resource leaks - without it, str would never be destroyed, and the memory it used would be lost forever.
  10. main() catches the exception.
  11. The program continues.

Note that even though we have the thrown exception, just as with function returns, the C++ run time calls destructors for allautomatic objects constructed since the beginning of the try block. The automatic objects are destroyed in reverse order of their construction. Automatic objects are local objects that have been declared auto or register, or not declared static or extern. An automatic object is deleted whenever the program exits the block in which the object is declared.

However, a function return just processes object put on the stack by that function, whereas the throw statement processes objects put on the stack by the entire sequence of function calls between the try block and the throw. Without the unwinding-stack feature, a throw would leave destructors uncalled for automatic class objects placed on the stack by intermediate function calls.

标题基于Spring Boot的博客系统设计与实现研究AI更换标题第1章引言介绍基于Spring Boot的博客系统的研究背景、意义、国内外现状及论文创新点。1.1研究背景与意义阐述博客系统在当前网络环境中的重要性及Spring Boot框架的优势。1.2国内外研究现状分析国内外博客系统的发展现状及Spring Boot的应用情况。1.3研究方法以及创新点概述本文采用的研究方法及在博客系统设计中的创新点。第2章相关理论总结Spring Boot框架、博客系统设计原理及相关技术。2.1Spring Boot框架基础介绍Spring Boot的核心特性、依赖管理和自动配置。2.2博客系统设计原理阐述博客系统的基本功能模块、数据流和用户交互。2.3相关技术简述数据库技术、前端技术及安全技术在博客系统中的应用。第3章博客系统需求分析对博客系统的功能需求、性能需求及用户需求进行详细分析。3.1功能需求分析列举博客系统所需的核心功能,如文章发布、评论管理等。3.2性能需求分析分析博客系统的响应时间、并发处理能力等性能指标。3.3用户需求分析调研不同用户群体对博客系统的期望和需求。第4章博客系统设计详细描述博客系统的架构设计、数据库设计及界面设计。4.1系统架构设计给出系统的层次结构、模块划分及模块间交互方式。4.2数据库设计设计数据库表结构、字段及关系,确保数据完整性和一致性。4.3界面设计设计用户友好的界面,包括文章列表、文章详情、评论区等。第5章博客系统实现阐述博客系统的开发环境搭建、功能实现及测试过程。5.1开发环境搭建介绍开发所需的软件、硬件环境及配置步骤。5.2功能实现详细描述各个功能模块的实现过程,包括代码示例。5.3系统测试对博客系统进行功能测试、性能测试及安全测试。第6章结论与展望总结博客系统的设计与实现成果,提出未来改进方向。6.1研究结论概括博客系统的主要功能、性能
最新扫码点餐外卖配送餐饮小程序系统源码 系统功能: 1.支持多平台:微信小程序,支付宝小程序,和H5平台,页面可以后台DIY管理。 2.小程序页面支持后台DIY,后台修改后即可同步到发布的小程序页面。 3.点单模式:支持堂食,外卖,打包自取。 4.下单:支持多人同时,中途加菜,退单等。 5.订单提醒:支持小程序订阅消息,公众号模板消息,小票打印。 6.结算方式:微信,支付宝,余额,餐后线下等。 7.商品管理:单,多规格,起售数量,打包费,库存销量推荐等操作。 8.门店管理:餐桌,店员,口味,云设备,二维码,wifi,预约订桌等管理。 9.会员卡:付费开会员,积分等级,等级折扣,升级赠送,卡面定制等。 10.积分签到:签到赠送积分或余额,添加签到任务等。 11.会员充值:前后台充值操作,充值套餐,充值送余额,充值送优惠券等。 12.优惠券:现金券(满减),折扣券(满折),赠送券(满赠)等 13.财务统计:实时统计,七日趋势,日统计,月统计,指定时间段统计,报表下载,打印等。 14.小票打印:易联云,飞鹅,大趋等品牌打印机。(其它可扩展) 15.第三方配送:河马跑腿,闪送,UU跑腿,顺丰同城,达达配送,码科配送(其它可扩展) 16.小票打印:易联云,飞鹅,大趋。(其它可扩展) 17.云存储:腾云,阿里,七牛(其它可扩展) 18.短信:腾云,阿里,七牛(其它可扩展) 仅供研究学习使用!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值