曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 4981|回复: 0
打印 上一主题 下一主题

异常与栈解退(unwinding the stack)

[复制链接]

716

主题

734

帖子

2946

积分

超级版主

Rank: 9Rank: 9Rank: 9

积分
2946
跳转到指定楼层
楼主
发表于 2013-12-30 11:50:49 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
C/C++ 通常都是通过将信息放在栈中处理函数调用的。在程序调用一个函数时,同时会将函数的返回地址放到栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里继续执行。此外,程序还将被调函数的参数放到栈中,这些栈中的参数被视为自动变量。如果被调函数还调用了其它函数,那后者的信息也以同样的方式添加到栈中,依此类推。当函数结束时,程序流程将跳到该函数被调用时存储的返回地址处,同时栈顶的元素被释放。因此,函数都通常返回到调用它的函数,依此类推,同时每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用。(图-1)

现在假设函数出现了异常而终止(并不是通过 return 来正常返回)。这时候,程序也将释放栈中的内存,但不会在栈中的第一个返回地址哪里停止,而是继续释放栈,直到找到一个位于 try 块中的返回地址,如下图所示:
(图-2)
随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为栈解退

引发(或称为抛出, throw)机制的一个非常重要的特征是,和函数返回一样,对于栈中的自动类对象,类的析构函数将被调用。然而,函数返回仅仅处理该函数放在栈中的对象;而 throw 语句则处理 try 块和 throw 之间整个函数调用序列放在栈中的对象(参考 图-2 的返回标示线)。如果没有栈解退这种特性,则引发异常后,对于中间函数放在栈中的自动类对象,其析构函数将不会被调用。

下面示例程序演示了栈解退。

exc_mean.h
[C++] 纯文本查看 复制代码
// exc_mean.h  -- exception classes for hmean(), gmean()
#include <iostream>

class bad_hmean
{
private:
	double v1;
	double v2;
public:
	bad_hmean(double a = 0, double b = 0) : v1(a), v2(b){}
	void mesg();
};

inline void bad_hmean::mesg()
{
	std::cout << "hmean(" << v1 << ", " << v2 << "): "
		<< "invalid arguments: a = -b\n";
}

class bad_gmean
{
public:
	double v1;
	double v2;
	bad_gmean(double a = 0, double b = 0) : v1(a), v2(b){}
	const char * mesg();
};

inline const char * bad_gmean::mesg()
{
	return "gmean() arguments should be >= 0\n";
}



error5.cpp
[C++] 纯文本查看 复制代码
//error5.cpp -- unwinding the stack
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#include <string>
#include "exc_mean.h"

class demo
{
private:
	std::string word;
public:
	demo(const std::string & str)
	{

		word = str;
		std::cout << "demo " << word << " created\n";
	}
	~demo()
	{
		std::cout << "demo " << word << " destroyed\n";
	}
	void show() const
	{
		std::cout << "demo " << word << " lives!\n";
	}
};

// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
double means(double a, double b);

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;

	double x, y, z;
	{
		demo d1("found in block in main()");
		cout << "Enter two numbers: ";
		while (cin >> x >> y)
		{
			try {                  // start of try block
				z = means(x, y);
				cout << "The mean mean of " << x << " and " << y
					<< " is " << z << endl;
				cout << "Enter next pair: ";
			} // end of try block
			catch (bad_hmean & bg)    // start of catch block
			{
				bg.mesg();
				cout << "Try again.\n";
				continue;
			}
			catch (bad_gmean & hg)
			{
				cout << hg.mesg();
				cout << "Values used: " << hg.v1 << ", "
					<< hg.v2 << endl;
				cout << "Sorry, you don't get to play any more.\n";
				break;
			} // end of catch block
		}
		d1.show();
	}
	cout << "Bye!\n";
	// cin.get();
	// cin.get();
	return 0;
}

double hmean(double a, double b)
{
	if (a == -b)
		throw bad_hmean(a, b);
	return 2.0 * a * b / (a + b);
}

double gmean(double a, double b)
{
	if (a < 0 || b < 0)
		throw bad_gmean(a, b);
	return std::sqrt(a * b);
}

double means(double a, double b)
{
	double am, hm, gm;
	demo d2("found in means()");
	am = (a + b) / 2.0;    // arithmetic mean
	try
	{
		hm = hmean(a, b);
		gm = gmean(a, b);
	}
	catch (bad_hmean & bg) // start of catch block
	{
		bg.mesg();
		std::cout << "Caught in means()\n";
		throw;             // rethrows the exception 
	}
	d2.show();
	return (am + hm + gm) / 3.0;
}


在上面程序中,main() 调用了 means() ,该函数用来计算算术平均数,调和平均数以及几何平均数,而 means() 又调用了 hmean() (计算调和平均数)和 gmean() (计算几何平均数) 。

在 main() 和 means() 中都创建了 demo 类型对象,该对象用来输出一些信息,指出什么时候构造函数和析构函数被调用了,这样容易从中了解发生异常时这些对象都如何被处理的。

在 main() 中的 try 块能够捕获 bad_hmean 和 bad_gmean 两种异常,而函数 means() 中的 try 块只能捕获 bad_hmean 异常。

在 means() 中的 catch 块里,单独使用了一个 throw 语句,它用来重新引发异常


测试程序的输出:


程序说明:
在第一次的输入(6 和 12)中,首先在 main() 中创建了一个 demo 对象。接着调用 means() 函数,它又创建了另一个 demo 对象。means() 用 6 和 12 来调用了 hmean() 和 gmean(),它们将结果返回给 means() ,后者计算一个结果((am + hm + gm) / 3.0;)并将其返回。在返回借过钱,means() 调用了 d2.show(),在返回结果后,means() 也就执行完毕了,此时自动为 d2 调用析构函数。

第第二次的测试输入(6 和 -6) 中,,means() 创建了一个新的 demo 对象,并将值传递给 hmean() ,此时会引发 bad_hmean 异常,该异常被 means() 中的 catch 块捕获,从输出:
hmean(6, -6) : invalid arguments: a = -b
Caught in menas()

可以看到这一点。该 catch 块中的 throw 语句将导致 means() 终止执行,这个重新引发的异常会传递给 main() 函数。语句 d2.show() 没有被执行,这表明了 means() 被提前终止了。但需要注意的是,还是为 d2 调用了析构函数,从输出:
demo found in means() destroyed

可以看到这一点,这也是非常重要的一点:程序进行栈解退以回到能够捕获异常的地方时,将释放栈中的自动存储变量。如果变量是类对象,将为该对象调用析构函数

另外,通过 throw 重新引发的异常在传递给 main() 函数后,main() 中有合适的 catch 块捕获它并对其进行了处理,从输出结果也能看到这一点:
hmean(6, -6): invalid arguments: a = -b
Try again.


在第三次循环的输入(6 和 -8)中。同样,means() 创建了一个新的 demo 对象,然后将参数传递给 hmean(),该函数处理这两个参数时不会有什么问题,但是传递给 gmean() 时就会引发异常了。由于 means() 不能捕获 bad_gmean 异常,因此异常被传递给 main(),同时不再执行 means() 中的其它代码。同样,在程序进行栈解退时,将释放局部的动态变量,因此 d2 调用了析构函数。

最后,在 main() 中的 bad_gmean 异常处理捕获了该异常,循环结束。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-4-30 12:05 , Processed in 0.077255 second(s), 24 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表