如何快速定位一个函数的返回点

如何快速定位一个函数的返回点,这对于一个比较短小精悍的函数来讲,从来就不是问题,但是假设我们有一个名为LongFunction的1000行长的函数, 调用如下:

bool bSuccess = LongFunction();
assert(bSuccess);

在运行中第二行弹出一个assert,我们知道肯定是LongFunction内部运行中出了什么问题导致其返回false。那么它内部出了什么问题,是在哪一行出错导致返回的?这恐怕不是一件容易的事,要知道这是一个1000行的函数,而且极有可能有很多的返回点。

我想这应该是我们日常工作中常见的问题,1000行的长函数在一些大型的系统中,老代码中应该还是不少见的。当然,有些朋友会强烈的认为这样的函数必须要重构,理由是~~~(此处略去500字)。的确,重构的好处是显而易见的,但很多时候由于资源,时间以及复杂度上的考虑,是不被采纳的。所以这里我们不考虑重构,只想找出一个能快速定位到函数返回点的方法。

我们先来分析一下可能的方案:

  • 单步执行
    这是最直接也是最浪费时间的方法,虽然你总能找到那个返回点,但显然,程序员是不会这么做的。

  • 搜索并打断点
    搜索函数中所有的"return"点,并在每一处设断点。这比第一点有效多了。虽然我们可以用正则表达式非常精确的定位到每一处真正的"return",但如果每次遇到这个情况都要搜一次,设一次,也比较麻烦。而且这种方法也有一点小瑕疵,假设以下代码:
    if(a != b) return false; 

    我们会在这一行设上断点,执行时也会break进去,但这不一定是真正的返回点。
  • 重定义return关键字
    #define return TRACE(__LINE__); return;

    如果我们的代码内建了这种机制,我只要看一下输出窗口的打印出的行号,就知道在哪里返回了。但是不能处理这个情况:
    if(bOK) return true;

  • 自定义宏替换return
    #defineRETURN(value) {TRACE(__LINE__); return value;}

    的确,这是能工作的,但这要求我们修改所有现存的代码,更糟的是,以后编程也需要使用这个RETURN,让这个丑陋的RETURN成为guideline,我想大家都不愿意。
  • 返回时构造
         struct ReturnType{ ReturnType(bool){ } };
         ReturnType LongFunction();

    我们把LongFunction的返回类型改为ReturnType类,这个类的构造函数以返回类型为参数。这样,LongFunction返回时就会构造ReturnType,我们只要在其构造函数中设断点,在callstack中就能看到LongFunction是在哪里返回的。有的朋友可能会觉得这样对于不同的返回类型,就要写不同的构造函数,我们可以有两种方法来解决这个问题:
     // 1. 变参
         struct ReturnType{ ReturnType(...){ } };
         // 2. 模板构造函数
         struct ReturnType
         {
         template < typename T > ReturnType(T t){}
         };
         
    这个方案的真正问题在于要求我们修改函数返回参数,这种接口的改动影响太大。
  • 返回时析构
     class ReturnMonitor
         {
         ~ReturnMonitor(){}
         };
         bool LongFunction()
    { ReturnMonitor mon; // Function body }
    在资源管理中我们经常会用这种方式(RAII),现在我们利用函数返回时会调用析构函数这个特性,在析构函数中设断点,就能在callstack中看到返回点。这还有一个优点就是在LongFunction调用过程中如果出现异常,也能被捕捉。

根据以上分析,我认为有两个方案只要稍加修饰,就能成为比较不错的候选方案:
第一个是"搜素并打断点",我们可以利用IDE的集成功能自动化这个步骤,比如说在Visual Studio中,我们可以写一个宏,或者写个插件来做这件事件,只要选中函数一点按钮,所有"return"自动打上断点。

第二个是"返回时析构",我们可以定义以下宏:

#ifdef _DEBUG
#define RETURN_MONITOR ReturnMonitor mon;
#else
#define RETURN_MONITOR
#endif

这样对于我们代码中比较长的,较难调试的长函数,就可以在函数开始加上RETURN_MONITOR,并且不影响release版本。

posted @ 2009-10-08 19:37  lzprgmr  阅读(1589)  评论(4编辑  收藏  举报

黄将军