8

在 C 和 C++assert中是一个非常重量级的例程,写入错误stdout并终止程序。在我们的应用程序中,我们实现了一个更健壮的 assert 替代品,并赋予它自己的宏。已尽一切努力assert用我们的宏替换,但仍有许多方法assert可以重新引入(例如,来自内部第三方库、幼稚注入等)

关于我们如何减少、限制甚至消除使用的任何建议assert?最好的答案将是编译器可以为我们捕获的答案,这样我们就不必像目前那样照看代码库了。

4

10 回答 10

14

实际上,我不确定我是否真的理解这个问题。断言只有在它们关闭时才会变得昂贵,无论如何这都很好,因为您现在处于异常情况。

assert仅在调试版本中启用,因此请使用第三方库的发布版本。但实际上,断言不应该每时每刻都发生。

于 2009-12-04T00:54:28.047 回答
2

这将取决于(至少部分)你正在改变什么。假设你不介意它打印出它的正常消息,并且最想摆脱它的调用abort(),你可以考虑不理会assert(),而是定义你自己的abort().

从理论上讲,这样做是不可移植的——但实际上,abort()这是标准库中一个相当正常的函数,如果你链接你自己的函数,你就会得到它的行为。有时(尤其是某些 Microsoft 链接器)您必须做一些工作才能让链接器配合abort()使用您的链接器替换它们,但这很少非常困难。

于 2009-12-04T01:10:08.563 回答
2

改进内置的断言工具(提供堆栈跟踪、核心转储,谁知道)可能很方便。在这种情况下,如果您在让开发人员遵循您拥有的任何标准时遇到问题(例如“而不是assert()使用SUPER_ASSERT()”或其他任何标准),您可以将自己的assert.h头文件放在编译器运行时头文件目录之前的包含路径中。

这几乎可以保证任何使用标准assert()宏的人都会遇到编译器错误或获得您的断言功能(取决于您的assert.h标头所做的事情)。

于 2009-12-04T07:51:59.590 回答
2

我认为你的问题是完全有效的。如果您已经实现了自己的错误处理,您可能希望:

  1. 即使在发布版本中也总是触发断言。
  2. 在断言触发的情况下实施更好的错误报告。您可能想要发送错误报告或写入日志文件。

话虽如此,我看不到任何始终有效的解决方案。

  • 如果幸运的话,第三方库使用 ASSERT 宏,您可以重新定义自己,只要定义此宏的文件具有某种#pragma once#ifndef __HEADERFILE_H__ #define __HEADERFILE_H__防止多重包含的规定。单独包含头文件,重新定义 ASSERT 就可以了。

  • 如果它们直接包含 assert.h 或 cassert 你只能修补我猜的代码。进行最少的代码更改,将更改保存为补丁文件,并在更新库时希望补丁仍然有效。将补丁添加到版本控制。

如果这不起作用,请重新考虑是否真的需要第三方库中的内部断言。仅发布发布版本,这摆脱了断言,并添加您的断言以检查代码中的正确性。检查返回值的有效性。如果触发了这样的 ASSERT,您仍然可以深入研究第三方代码以查看导致问题的原因。

于 2009-12-04T10:31:10.260 回答
2

我认为这个问题是有效的。

如果触发,我自己的断言会扩展为 asm("int3"),这相当于断点。我还发现这对于调试比简单的终止更有用。

我只是将其称为“ASSERT()”而不是普通的“assert()”,并且完全避免使用 assert()。

于 2009-12-04T10:59:54.707 回答
1

最明显的方法似乎是为您自己的 assert 版本提供自己的名称,与assert(). 然后,您可以搜索文本、查看链接器消息等,以查找文字字符串“_assert”,当您看到它时就知道您遇到了问题。

在我自己的代码中,我总是使用Assert(),它扩展为我自己的执行断言的函数,或扩展为((void)0)用于发布构建。编译器会将((void)0)表达式变为空,但它仍然算作表达式。因此

Assert(3 == x);

会变成

((void)0);

分号有地方可去。

顺便说一句,我曾经开发过一个 GUI 应用程序,其中断言是一个特殊的 GUI 模式弹出对话框。您有三个选择:忽略、永远忽略或中断。Ignore 将忽略断言并继续运行。Ignore forever 将设置一个标志,直到您在调试器中重新启动程序,该断言将不再触发。Break 将允许断言闯入调试器。

我不记得他们如何保证每个断言都有自己的标志。也许当您编写 Assert() 调用时,您必须指定一个唯一的整数?如果它比这更自动就好了。我很确定实际的实现是一个位向量,当你选择永远忽略时它会设置位。

于 2009-12-04T00:52:30.337 回答
1

assert()对于发布代码( ),通常#define((void)0)#define NDEBUG,因此根本没有开销

使用测试版本时,性能开销是否会损害您实现测试的能力?

于 2009-12-04T00:58:04.913 回答
1

如果源代码在您的控制之下:

#define NDEBUG
// Before
#include <assert.h>
// Or other header that includes assert.h

或者使用预编译头文件或编译选项来定义NDEBUG.

对于第三方二进制文件,请使用它们的发布版本。

于 2009-12-04T01:04:31.190 回答
1

您似乎错过了第三方代码很可能是在“标准”assert行为的假设下编写的这一事实。即代码期望程序在断言失败时终止。如果断言条件被破坏,断言之后的代码通常不能也不会正常工作。在 100 例中的​​ 99 例中,它根本不起作用。在 100 次中的 99 次中,它会简单地崩溃,即程序无论如何都会终止。

相信通过覆盖assert第三方代码中的行为会以某种方式延长程序的生命周期,这种想法充其量是幼稚的。

于 2009-12-04T07:59:55.083 回答
0

在库头中查找assert(假设它们是文件系统上的真实文件)并将其替换为无效的东西

// #define assert(condition) ... /* old definition */
#define assert(condition) ((condition) & "PLEASE DO NOT USE ASSERT" = 42)
于 2009-12-04T00:55:58.287 回答