如何在发布版本中编译依赖于仅调试代码的断言()?

Posted

技术标签:

【中文标题】如何在发布版本中编译依赖于仅调试代码的断言()?【英文标题】:How to have assert()s that depend on debug-only code compile in a Release build? 【发布时间】:2013-04-01 17:25:32 【问题描述】:

等等! - 这并不像听起来那么愚蠢的问题。标题很简洁。

我有一些调试代码来验证数据结构的正确性和一些断言检查这种正确性,我只想在调试版本中启用:

$ifdef DEBUG
  function Sorted : Boolean;
  function LinearSearchByValue(const T : TType) : NativeInt;
$endif

然后在方法中:

assert(Sorted);

例如。

在我启用断言的调试版本中,一切都很好。但是,在我的带有断言disabled 的发布版本中,assert(Sorted); 行会导致编译器错误E2003 Undeclared identifier: 'Sorted'。非常正确,没有声明标识符,但断言也被关闭,不应该被评估或生成任何代码。 (试图通过声明方法但没有实现来欺骗断言会导致正常错误“不满足前向或外部声明”。它显然也在寻找方法体。)

这会导致一些混乱的代码,其中必须声明根本不应该存在于 Release 构建中的方法并具有一个主体,以便编译关闭的断言。

如何在 Delphi 2010 中声明仅存在于调试版本中的方法,并在断言中使用那些也应该只存在于调试版本中的方法?

更多信息:

我尝试用$ifopt C+ 包装方法声明,它会检查断言是否已打开。对assert 的调用仍因“未声明的标识符”而失败。

编译器选项肯定是关闭了断言。 (我检查了:))

我已尝试将使用这些方法的assert 调用与$ifdef DEBUG 封装在一起。但是,这是混乱的,不应该是必需的。有一次它让我担心即使在发布版本中也会编译断言,出于性能原因,我根本不想要它们。 (这不会发生 - 不会生成断言代码。)

我目前的策略是一直声明这些方法,但在 Release 构建中,如果定义方法体,则将方法体取出并用虚拟结果填充它。这里的目的是编译所有断言,但方法的开销尽可能小,并且它们的返回值(如果它们实际上在发布版本中被调用)显然是错误的。

在 Delphi 中是否有任何等效于 C/C++ 样式的宏,其中 ASSERT(x) 宏在发布版本中将被简单地定义为空,导致编译器既看不到也不关心里面的语句断言?这是在 C++ 中使用宏的少数简洁方法 (IMO) 之一。

因此,虽然没有生成断言,但它们已被编译。这又回到了我的问题:如何最好地混合仅调试方法和断言,并发布构建?

【问题讨论】:

我想你已经列举了你的选择。你只需要决定使用哪一个。 我不会使用虚拟结果,我会raise EAssertionFailed,因为你永远不应该到达那里。事实上,我有一个名为 RaiseAssertionFailed 的实用程序函数可以做到这一点。 @Rob Kennedy,你错了。 Assert 编译器魔术具有其他情况下不可用的独特属性,是检查始终为真条件的正确工具。 我之前的评论是正确的,@User。 Mad except、JclDebug 和 Eureka Log all 允许程序从异常中发现文件名、行号和 很多 附加信息,而 none i> 需要使用Assert 来获取。 您不会在运行时引发异常,因为您永远不会调用该代码。我的代码里到处都是这样的东西。例如,枚举类型的 case 语句的 else 子句。我总是把RaiseAssertionFailed 放在那里。当然它永远不会执行。除了我犯错的时候。然后我就知道了。这就是我想要的。 【参考方案1】:

不要从您的发布版本中排除代码。将代码保留在那里,并无条件编译。

您反对将代码存在于发布版本中的论点是它“混乱”。但是你已经写好了代码,所以不管编译不编译都会很乱。你也可以让编译器编译它;毕竟,编译额外的代码不会花费太多时间。

试图排除与断言相关的代码只会使您的代码更混乱,因为它需要条件编译指令。

断言和调试信息是正交设置。不调试时可以启用断言,反之亦然。

另一种方法是将与断言相关的代码移动到单元测试中。然后它们会自动从您的应用程序的所有版本中排除,但它们仍可用于测试。

【讨论】:

即使在 $C- 状态下,Assert 及其参数也参与了编译过程,因此即使 Assert 调用不会在 $C- 中生成任何代码,也不能有未声明的符号。所以解决方案是$IFOPT C+Assert(ThisSymbolHasToBeDeclared)$ENDIF 是的,@User,他们正在参与编译,但那又怎样?我不是建议有任何未声明的符号。我建议根本没有 no 条件编译。保持Sorted 正常声明,并在Assert 语句中正常调用它。编译器不会生成任何目标代码。 我同意 Rob 的观点。让编译器编译它。由于您没有在发布版本中引用它,因此链接器只会将其丢弃。 @RobKennedy:我部分同意 - 相信编译器不会链接它们通常是可以的。但是这种情况下的条件编译是因为方法不是普通类接口的一部分。它们仅用于调试时检查,如果不小心将它们包含在发布代码中是很危险的。因此,它们被定义为仅存在于 Debug 中。 IMO 发布版本应该不可能存在这些方法:未声明,未实现,不在 IDE 洞察中等。这可能是一个特殊情况,但我怀疑这种情况非常罕见,以至于它通常是无效的。 @DavidM 如果您想将这些内部方法保留在发布版本中客户端看到的公共接口之外,请将这些方法标记为私有,或者将它们移动到您的类实现的接口中。跨度> 【参考方案2】:

在编译过程的链接步骤中,断言通常被“省略”或不包含在输出的可执行代码中。传递给断言函数的源代码符号和表达式必须在编译步骤中定义,以便编译器可以解析和生成断言及其表达式参数的代码。

assert 是否包含在输出 exe 中由链接器在将代码复制到输出文件时确定。根据编译器的不同,传递给 assert 的表达式可能仍然包含在可执行代码中,即使不包含对 assert 函数的调用。

正如在其他答案和许多 cmets 中所指出的,断言并不是调试所独有的。断言在发布代码中也很有价值,可以验证绝对不会发生的情况。

允许您在发布代码中保留断言同时使断言表达式中使用的函数仅存在于调试版本中的一种解决方案是为发布版本定义调试函数的无操作存根。像这样:

// Declaration:
  private function Sorted: Boolean;

// Implementation

$ifdef DEBUG
  function Sorted : Boolean;
  begin
// work work work
  end;
$else
  function Sorted: Boolean;
  begin
  end;
$endif


// used in an assertion:

  assert(Sorted);

这允许您在调试代码和发布代码中使用断言,而不会使用断言周围的 ifdef 包装器污染您的源代码,并保证您的发布代码中不存在调试实现。

【讨论】:

以上是关于如何在发布版本中编译依赖于仅调试代码的断言()?的主要内容,如果未能解决你的问题,请参考以下文章

fortran调试断言失败怎么解决?

当查询依赖于仅使用 PHP 的 $_GET 时,如何对表输出进行排序?

assert断言检测

如何在 Visual Studio 2017 中禁用作为调试错误的失败断言?

第39问:如何编译 MySQL 的调试版本

关于QtCreator中三种不同编译版本 debugreleaseprofile 的区别