如何确定long函数返回的位置

假设有一个名为LongFunction的1000行代码的函数,我们使用它:

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

这里我在调试时得到一个断言,我知道LongFunction有问题,所以我需要找到函数遇到问题的地方并返回:

  1. 我可以一步一步地调试它,它可以工作,但是很费时,我们不这样做。

  2. 我可以搜索关键字“返回”(或者更精确的搜索使用RegExp),并在那些返回时设置断点,它应该更快,但它仍然是乏味的手动工作,无法自动化。

  3. #define返回TRACE( LINE ); 返回

它有效,但有以下问题:

  • 它会打印太多冗余信息,因为经常使用返回。(或者我们可以使用一些EnvVar来打开或关闭它)
  • 不适用于以下情况:if(bOK)返回true;

您对如何查明问题还有其他创意吗?

编辑:以下是一些让我们关注问题的细节。

  1. 它是关于C ++,而不是平台规范。

  2. 我们不想重构函数(是的,我知道我们应该),我们甚至不想更改任何代码 – 此时我们只想提供一些工具来简化我们的应用程序调试。 我也相信这应该是一个共同的要求,难道你不碰到这个吗?

  3. LongFunction()有多个退出点,并且返回类型不是必需的bool(HRESULT,用户定义的错误代码……)

编辑 :当前讨论的摘要:
我们有一些争议:

  1. 你应该重构这个function。
    是的,每个人都知道我们应该,但这不是重点。如果我打电话重构函数,我不会在这里问这个问题。

  2. 找到LongFunction()返回失败的地方没有帮助。
    我总是首先找到发生错误的地方,知道发生了什么,我很好奇为什么这没有用,你在这种情况下做了什么? (假设我已经熟悉该函数的工作原理)

我们有2个合理的解决方案:

  1. 来自Crashworks的ReturnMarker,函数中的堆栈对象将在函数返回时进行破坏,在析构函数中设置断点将显示它在debuger中返回的位置

  2. 来自Binary&Sadsido的CMyBool(x),将LongFunction的返回类型更改为可以从bool构造的CMyBool,从LongFunction返回将构造该对象,因此只需在构造函数中设置断点即可。

显然你应该重构这个函数,但在C ++中你可以使用这个简单的权宜之计在五分钟内解决这个问题:

 class ReturnMarker { public: ReturnMarker() {}; ~ReturnMarker() { dummy += 1; //<-- put your breakpoint here } static int dummy; } int ReturnMarker::dummy = 0; 

然后在函数顶部实例化一个ReturnMarker。 当它返回时,该实例将超出范围,您将点击析构函数。

 void LongFunction() { ReturnMarker foo; // ... } 

听起来好像是重构LongFunction()的时候了……

一个1000行function是一个坏代码气味。 花时间将其重构为更小,更易维护的function。 您可以在遇到错误时找到错误,这对未来将是一次值得的投资。

这是C还是C ++?

如果是C ++,创建一个包含bool(比如CMyBool)的新类,它会自动转换为bool。

现在让LongFunction返回CMyBool(快速搜索和替换会将LongFuntion中的所有返回值更改为“返回CMyBool(x)”。

现在在CMyBool的ctor中设置一个断点,调试器现在将在创建CMyBool时停止,这将在LongFunction中的正确返回语句上。

自动强制转换为bool将阻止CMyBool破坏使用CMyBool的代码。

这将使您了解最初的问题,但更大的问题是LongFunction需要重构。

希望这可以帮助。

如果你的问题只是普通的懒惰(没有错误),请确保LongFunction中的所有return语句都是

 return(value); 

而不是

 return value; 

(例如使用正则表达式搜索和替换)

然后,使用一个slighlty修改的预处理器宏比原始建议:

 #define return(value) { if (!value) TRACE(__LINE__); return(value); } 

…甚至

 #define return(value) { assert(value); return(value); } 

……或者你认为合适的任何东西

你还没有说过你的平台。 根据LongFunction的内容,以下是我如何使用gdb来解决这个问题:

让我们假设你的文件’f.cc’有以下几行:

 1: bool LongFunction () { /* ... */ } 2: 3: void bar () 4: { 5: bool bSuccess = LongFunction (); 6: assert (bSuccess); 7: } 

以下是gdb中的步骤:

  1. 在与断言相同的行上添加断点: break f.cc:6

  2. 当bSuccess为false时,为该断点添加条件: condition 1 (bSuccess==0)

  3. 运行程序直到达到该断点

  4. 在函数体的开头设置断点: break f.cc:4

  5. 跳回到该位置(它将在断点处停止): jump f.cc:4

  6. 调试LongFunction的内容,看看它失败的原因。

我之所以说它取决于LongFunction的内容,是因为如果LongFunction从流中读取输入,或修改全局变量等,那么第二轮的行为可能会有所不同。 您应该将上述步骤视为代码如下:

 3: void bar () 4: { 5: bool bSuccess = LongFunction (); 5: bSuccess = LongFunction (); 6: assert (bSuccess); 7: } 

它是visual studio我会在断言上放置一个断点,然后使用堆栈跟踪窗口单击下一行,然后将进入该方法的退出点。

 #define return { TRACE(LINE); return; } 

这解决了你的问题4。

就其他问题而言,这就是编码的问题。 这就是为什么许多系统在出现问题时将更复杂的错误(例如来自COM对象的HRESULT)和/或垃圾邮件返回到调试流的原因。

但是应该重新考虑1000行函数。 正如您所看到的那样,长期function难以维护。

编辑:以下工作会比上面的更好吗?

 #define return TRACE(LINE), return 

喝了几杯,所以可能没有。

“我可能会逐步调试它,它可以工作,但很耗时,我们不会这样做。”

你怎么调试?

我曾经调试过:

1)找到一组重现问题的参数。

2)使用该组参数逐步执行代码。

如果有1000行代码,那么如果不完全知道函数的function和应该做什么,你将如何“重构”。

如果不单独执行该function,您将如何做到这一点。 我认为这是一个带有良好调试器的IDE。

坦率地说,我发现这个问题几乎是一种非常悲伤的问题。

1982年,我的任务是修复一个破碎的流程图程序。 该程序是用机器相关的Harris FORTRAN编写的(我现在不记得它是在FORTRAN IV还是FORTRAN 77中),由一个1100行的主程序,一个900行子程序和大约十几个子程序组成。长10到20行。

哦,程序中几乎没有空格(空白行),评论根本没用。

我花了160个小时 – 四个星期,全部时间,我的桌子上没有其他东西 – 弄清楚那些代码足以进行适当的修理。

你处于类似的情况。 这将花费你真正的CPU时间投资,让你找到1000行的生物,足以解决所有可能出错的问题,以及如何解决它。

找到回报很容易。 对于Microsoft AbysmalC ++:搜索“return”的每个实例并在其前面加上

“printf(”\ n \ n — >>>在%d \ n \ n“,__LINE__行冲出);”

(或您系统上的等效物)。 显然,你不能自动这样做; 你必须看看当地的包围。 然后运行测试,看看它告诉你的样子。

事实是:在现实的计算世界里,几乎没有真正的例程,实际上需要1000行长。 在近40年的时间里,作为学生和专业人士,我只遇到了一个需要长于一个打印机页面(大约60行)的例行程序,而且这个例子非常特殊。 全部讲述了大约三页。

另一方面,我已经看到很多运行模块,编写它的人太懒,或者太无能,无法正确地考虑因素,维护程序员太懒,太无能,或者太害怕他们的经理回去重构它。

最后:考虑到这可能不是最后一次有人必须处理这个模块,而且可能不是你最后一次必须处理它。 这些东西往往是焦油宝宝:一旦你触摸它们,你永远不会脱离它们。 至少可能值得花时间进行重构,并且很可能从SCRATCH和第一原则重新设计/重写它。