在 C 和 C++assert
中是一个非常重量级的例程,写入错误stdout
并终止程序。在我们的应用程序中,我们实现了一个更健壮的 assert 替代品,并赋予它自己的宏。已尽一切努力assert
用我们的宏替换,但仍有许多方法assert
可以重新引入(例如,来自内部第三方库、幼稚注入等)
关于我们如何减少、限制甚至消除使用的任何建议assert
?最好的答案将是编译器可以为我们捕获的答案,这样我们就不必像目前那样照看代码库了。
实际上,我不确定我是否真的理解这个问题。断言只有在它们关闭时才会变得昂贵,无论如何这都很好,因为您现在处于异常情况。
assert
仅在调试版本中启用,因此请使用第三方库的发布版本。但实际上,断言不应该每时每刻都发生。
这将取决于(至少部分)你正在改变什么。假设你不介意它打印出它的正常消息,并且最想摆脱它的调用abort()
,你可以考虑不理会assert()
,而是定义你自己的abort()
.
从理论上讲,这样做是不可移植的——但实际上,abort()
这是标准库中一个相当正常的函数,如果你链接你自己的函数,你就会得到它的行为。有时(尤其是某些 Microsoft 链接器)您必须做一些工作才能让链接器配合abort()
使用您的链接器替换它们,但这很少非常困难。
改进内置的断言工具(提供堆栈跟踪、核心转储,谁知道)可能很方便。在这种情况下,如果您在让开发人员遵循您拥有的任何标准时遇到问题(例如“而不是assert()
使用SUPER_ASSERT()
”或其他任何标准),您可以将自己的assert.h
头文件放在编译器运行时头文件目录之前的包含路径中。
这几乎可以保证任何使用标准assert()
宏的人都会遇到编译器错误或获得您的断言功能(取决于您的assert.h
标头所做的事情)。
我认为你的问题是完全有效的。如果您已经实现了自己的错误处理,您可能希望:
话虽如此,我看不到任何始终有效的解决方案。
如果幸运的话,第三方库使用 ASSERT 宏,您可以重新定义自己,只要定义此宏的文件具有某种#pragma once
或#ifndef __HEADERFILE_H__ #define __HEADERFILE_H__
防止多重包含的规定。单独包含头文件,重新定义 ASSERT 就可以了。
如果它们直接包含 assert.h 或 cassert 你只能修补我猜的代码。进行最少的代码更改,将更改保存为补丁文件,并在更新库时希望补丁仍然有效。将补丁添加到版本控制。
如果这不起作用,请重新考虑是否真的需要第三方库中的内部断言。仅发布发布版本,这摆脱了断言,并添加您的断言以检查代码中的正确性。检查返回值的有效性。如果触发了这样的 ASSERT,您仍然可以深入研究第三方代码以查看导致问题的原因。
我认为这个问题是有效的。
如果触发,我自己的断言会扩展为 asm("int3"),这相当于断点。我还发现这对于调试比简单的终止更有用。
我只是将其称为“ASSERT()”而不是普通的“assert()”,并且完全避免使用 assert()。
最明显的方法似乎是为您自己的 assert 版本提供自己的名称,与assert()
. 然后,您可以搜索文本、查看链接器消息等,以查找文字字符串“_assert”,当您看到它时就知道您遇到了问题。
在我自己的代码中,我总是使用Assert()
,它扩展为我自己的执行断言的函数,或扩展为((void)0)
用于发布构建。编译器会将((void)0)
表达式变为空,但它仍然算作表达式。因此
Assert(3 == x);
会变成
((void)0);
分号有地方可去。
顺便说一句,我曾经开发过一个 GUI 应用程序,其中断言是一个特殊的 GUI 模式弹出对话框。您有三个选择:忽略、永远忽略或中断。Ignore 将忽略断言并继续运行。Ignore forever 将设置一个标志,直到您在调试器中重新启动程序,该断言将不再触发。Break 将允许断言闯入调试器。
我不记得他们如何保证每个断言都有自己的标志。也许当您编写 Assert() 调用时,您必须指定一个唯一的整数?如果它比这更自动就好了。我很确定实际的实现是一个位向量,当你选择永远忽略时它会设置位。
assert()
对于发布代码( ),通常#define
是((void)0)#define NDEBUG
,因此根本没有开销。
使用测试版本时,性能开销是否会损害您实现测试的能力?
如果源代码在您的控制之下:
#define NDEBUG
// Before
#include <assert.h>
// Or other header that includes assert.h
或者使用预编译头文件或编译选项来定义NDEBUG
.
对于第三方二进制文件,请使用它们的发布版本。
您似乎错过了第三方代码很可能是在“标准”assert
行为的假设下编写的这一事实。即代码期望程序在断言失败时终止。如果断言条件被破坏,断言之后的代码通常不能也不会正常工作。在 100 例中的 99 例中,它根本不起作用。在 100 次中的 99 次中,它会简单地崩溃,即程序无论如何都会终止。
相信通过覆盖assert
第三方代码中的行为会以某种方式延长程序的生命周期,这种想法充其量是幼稚的。
在库头中查找assert
(假设它们是文件系统上的真实文件)并将其替换为无效的东西
// #define assert(condition) ... /* old definition */
#define assert(condition) ((condition) & "PLEASE DO NOT USE ASSERT" = 42)