曲径通幽论坛

标题: 异常与栈解退(unwinding the stack) [打印本页]

作者: easy    时间: 2013-12-30 11:50
标题: 异常与栈解退(unwinding the stack)
C/C++ 通常都是通过将信息放在栈中处理函数调用的。在程序调用一个函数时,同时会将函数的返回地址放到栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里继续执行。此外,程序还将被调函数的参数放到栈中,这些栈中的参数被视为自动变量。如果被调函数还调用了其它函数,那后者的信息也以同样的方式添加到栈中,依此类推。当函数结束时,程序流程将跳到该函数被调用时存储的返回地址处,同时栈顶的元素被释放。因此,函数都通常返回到调用它的函数,依此类推,同时每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用。[attach]2422[/attach](图-1)

现在假设函数出现了异常而终止(并不是通过 return 来正常返回)。这时候,程序也将释放栈中的内存,但不会在栈中的第一个返回地址哪里停止,而是继续释放栈,直到找到一个位于 try 块中的返回地址,如下图所示:
[attach]2423[/attach](图-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 语句,它用来重新引发异常


测试程序的输出:
[attach]2424[/attach]

程序说明:
在第一次的输入(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 异常处理捕获了该异常,循环结束。







欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) Powered by Discuz! X3.2