Kra*_*lew 13 c c++ macros assert
重新定义断言宏是不是很邪恶?
有些人建议使用自己的宏ASSERT(cond)而不是重新定义现有的标准断言(cond)宏.但是如果你有很多遗留代码使用assert(),你不想让源代码改变,你想拦截,规范,断言报告,这没有用.
我已经做好了
#undef assert
#define assert(cond) ... my own assert code ...
Run Code Online (Sandbox Code Playgroud)
在上面的情况 - 代码已经使用断言,我想扩展断言失败的行为 - 当我想做像
1)打印额外的错误信息,使断言更有用
2)在断言上自动调用调试器或堆栈跟踪
通过实现SIGABRT信号处理程序,可以在不重新定义断言的情况下完成...,2).
3)将断言失败转换为抛出.
...这个,3),不能由信号处理程序完成 - 因为你不能从信号处理程序中抛出C++异常.(至少不可靠.)
为什么我想要断言抛出?堆叠错误处理.
我通常不会这样做,因为我希望程序在断言后继续运行(虽然见下文),但是因为我喜欢使用异常来提供更好的错误上下文.我经常这样做:
int main() {
try { some_code(); }
catch(...) {
std::string err = "exception caught in command foo";
std::cerr << err;
exit(1);;
}
}
void some_code() {
try { some_other_code(); }
catch(...) {
std::string err = "exception caught when trying to set up directories";
std::cerr << err;
throw "unhandled exception, throwing to add more context";
}
}
void some_other_code() {
try { some_other2_code(); }
catch(...) {
std::string err = "exception caught when trying to open log file " + logfilename;
std::cerr << err;
throw "unhandled exception, throwing to add more context";
}
}
Run Code Online (Sandbox Code Playgroud)
等等
即异常处理程序添加更多错误上下文,然后重新抛出.
有时我会打开异常处理程序,例如stderr.
有时我让异常处理程序推送到一堆错误消息.(显然,当问题内存不足时,这将无效.)
**这些断言异常仍然退出......**
评论这篇文章的人@IanGoldby说:"一个不退出的断言的想法对我没有任何意义."
以免我不清楚:我通常有这样的例外退出.但最终,也许不会立即.
而不是
#include <iostream>
#include <assert.h>
#define OS_CYGWIN 1
void baz(int n)
{
#if OS_CYGWIN
assert( n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin." );
#else
std::cout << "I know how to do baz(n) most places, and baz(n), n!=1 on Cygwin, but not baz(1) on Cygwin.\n";
#endif
}
void bar(int n)
{
baz(n);
}
void foo(int n)
{
bar(n);
}
int main(int argc, char** argv)
{
foo( argv[0] == std::string("1") );
}
Run Code Online (Sandbox Code Playgroud)
只生产
% ./assert-exceptions
assertion "n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin."" failed: file "assert-exceptions.cpp", line 9, function: void baz(int)
/bin/sh: line 1: 22180 Aborted (core dumped) ./assert-exceptions/
%
Run Code Online (Sandbox Code Playgroud)
你可能会这样做
#include <iostream>
//#include <assert.h>
#define assert_error_report_helper(cond) "assertion failed: " #cond
#define assert(cond) {if(!(cond)) { std::cerr << assert_error_report_helper(cond) "\n"; throw assert_error_report_helper(cond); } }
//^ TBD: yes, I know assert needs more stuff to match the definition: void, etc.
#define OS_CYGWIN 1
void baz(int n)
{
#if OS_CYGWIN
assert( n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin." );
#else
std::cout << "I know how to do baz(n) most places, and baz(n), n!=1 on Cygwin, but not baz(1) on Cygwin.\n";
#endif
}
void bar(int n)
{
try {
baz(n);
}
catch(...) {
std::cerr << "trying to accomplish bar by baz\n";
throw "bar";
}
}
void foo(int n)
{
bar(n);
}
int secondary_main(int argc, char** argv)
{
foo( argv[0] == std::string("1") );
}
int main(int argc, char** argv)
{
try {
return secondary_main(argc,argv);
}
catch(...) {
std::cerr << "main exiting because of unknown exception ...\n";
}
}
Run Code Online (Sandbox Code Playgroud)
并获得稍微更有意义的错误消息
assertion failed: n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin."
trying to accomplish bar by baz
main exiting because of unknown exception ...
Run Code Online (Sandbox Code Playgroud)
我不应该解释为什么这些上下文敏感的错误消息可能更有意义.例如,用户可能没有任何想法为什么要调用baz(1).它很可能是一个错误 - 在cygwin上,你可能需要调用cygwin_alternative_to_baz(1).
但是用户可能理解"条形"是什么.
是的:这不能保证有效.但是,就此而言,断言并不能保证工作,如果他们做的事情比调用中止处理程序更复杂.
write(2,"error baz(1) has occurred",64);
Run Code Online (Sandbox Code Playgroud)
甚至不能保证工作(在这个调用中有一个安全的错误.)
例如,如果malloc或sbrk失败了.
为什么我想要断言抛出?测试
我偶尔重新定义assert的另一个重要原因是为遗留代码编写单元测试,代码使用assert来发出错误信号,我不允许重写.
如果这段代码是库代码,那么通过try/catch包装调用很方便.查看是否检测到错误,然后继续.
哦,哎呀,我不妨承认:有时我写这个遗留代码.我故意使用assert()来发出错误信号.因为我无法依赖用户执行try/catch/throw - 实际上,通常必须在C/C++环境中使用相同的代码.我不想使用我自己的ASSERT宏 - 因为,不管你信不信,ASSERT经常发生冲突.我发现充斥着FOOBAR_ASSERT()和A_SPECIAL_ASSERT()丑陋的代码.不......简单地使用assert()本身是优雅的,基本上是有效的.并且可以扩展....如果可以覆盖assert().
无论如何,使用assert()的代码是我的还是来自其他人的代码:有时你希望代码失败,通过调用SIGABRT或退出(1) - 有时你想让它抛出.
我知道如何通过exit(a)或SIGABRT来测试失败的代码 - 比如
for all tests do
fork
... run test in child
wait
check exit status
Run Code Online (Sandbox Code Playgroud)
但是这段代码很慢.并不总是便携式.并且往往运行速度慢几千倍
for all tests do
try {
... run test in child
} catch (... ) {
...
}
Run Code Online (Sandbox Code Playgroud)
这可能比堆叠错误消息上下文更具风险,因为您可以继续操作.但是你总是可以选择cactch的例外类型.
元观察
我和Andrei Alexandresciu一起认为异常是报告想要安全的代码中错误的最有名的方法.(因为程序员不能忘记检查错误返回码.)
如果这是正确的......如果错误报告中存在相位变化,从退出(1)/信号/到异常......仍然存在如何使用遗留代码的问题.
而且,总体而言 - 有几种错误报告方案.如果不同的图书馆使用不同的方案,那么如何让它们共存.
Ton*_*roy 10
重新定义标准宏是一个丑陋的想法,你可以确定行为在技术上是未定义的,但最后宏只是源代码替换,很难看出它如何导致问题,只要断言导致你的程序退出.
也就是说,如果翻译单元中的任何代码在您的定义之后重新定义assert,那么您的预期替换可能无法可靠地使用,这表明需要特定的包含顺序等等 - 该死的脆弱.
如果您的assert替代代码没有exit,那么您就会遇到新的问题.有一些病态边缘情况,你的投掷想法可能会失败,例如:
int f(int n)
{
try
{
assert(n != 0);
call_some_library_that_might_throw(n);
}
catch (...)
{
// ignore errors...
}
return 12 / n;
}
Run Code Online (Sandbox Code Playgroud)
上面,值为0表示n开始崩溃应用程序而不是使用合理的错误消息停止它:将不会看到抛出消息中的任何解释.
我和Andrei Alexandresciu一起认为异常是报告想要安全的代码中错误的最有名的方法.(因为程序员不能忘记检查错误返回码.)
我不记得安德烈说的那样 - 你有报价吗?他当然非常仔细地考虑如何创建鼓励可靠异常处理的对象,但我从未听说过他/他曾经认为在某些情况下停止程序断言是不合适的.断言是强制不变量的一种常规方式 - 肯定有一条线可以用来表示哪些可能的断言可以继续,哪些不可以,但在该行的一侧,断言继续有用.
返回错误值和使用异常之间的选择是你提到的那种论证/偏好的传统理由,因为它们是更合法的替代品.
如果这是正确的......如果错误报告中存在相位变化,从退出(1)/信号/到异常......仍然存在如何使用遗留代码的问题.
如上所述,您不应尝试将所有现有exit()/断言等迁移到异常.在许多情况下,没有办法有意义地继续处理,抛出异常只会产生怀疑,问题是否会被正确记录并导致预期的终止.
而且,总体而言 - 有几种错误报告方案.如果不同的图书馆使用不同的方案,那么如何让它们共存.
如果这成为一个真正的问题,您通常会选择一种方法,并使用提供您喜欢的错误处理的层来包装不符合的库.
我编写了在嵌入式系统上运行的应用程序。在早期,我随意地在代码中使用断言,表面上是在代码中记录应该不可能的条件(但在某些地方是懒惰的错误检查)。
事实证明断言偶尔会被击中,但是没有人看到包含到文件和行号的消息输出到控制台,因为控制台串行端口通常没有连接任何东西。后来,我重新定义了assert宏,以便与其将消息输出到控制台,而无需通过控制台将消息通过网络发送到错误记录器。
无论您是否认为重新定义断言都是“邪恶的”,这对我们来说都很好。
| 归档时间: |
|
| 查看次数: |
3229 次 |
| 最近记录: |