定制 C++断言宏

发布于 2024-10-21 06:27:10 字数 1263 浏览 4 评论 0原文

我偶然发现了一篇内容丰富的文章: http://cnicholson.net/2009/02/愚蠢的-c-技巧-冒险-in-断言/ 它指出了我当前的调试宏套件中存在的大量问题。

如果您点击链接,则会在文章末尾附近给出宏最终版本的完整代码。

所呈现的一般形式是这样的(如果我在转换它时出错,请有人纠正我):

#ifdef DEBUG
#define ASSERT(cond) \  
    do \  
    { \  
        if (!(cond)) \  
        { \  
            ReportFailure(#cond, __FILE__, __LINE__, 0); \
            HALT(); \
        } \  
    } while(0)  
#else  
#define ASSERT(cond) \  
    do { (void)sizeof(cond); } while(0) 

在考虑用我所学到的知识修改我的代码时,我注意到该文章的评论中发布了一些有趣的变化:

一是您不能将此宏与三元运算符一起使用(即 cond?ASSERT(x):func()),建议将 if() 替换为三元运算符和一些括号以及逗号运算符。后来另一位评论者提供了这一点:

#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif

我认为在这种情况下使用逻辑和 && 特别聪明,在我看来,这个版本比使用 if 的版本更灵活 甚至三元 ?:。更好的是,assert_handler 的返回值可以用来确定程序是否应该停止。虽然我不确定为什么是 (HALT(), 1) 而不是 HALT()

第二个版本有什么我忽略的特别缺点吗?它取消了宏周围的 do{ } while(0) ,但这里似乎没有必要,因为我们不需要处理 if

你怎么认为?

I stumbled upon an informative article: http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/
which pointed out a great number of problems that exist in my current suite of debugging macros.

The full code for the final version of the macro is given near the end of the article if you follow the link.

The general form as presented is like this (somebody please correct me if i am wrong in transposing it):

#ifdef DEBUG
#define ASSERT(cond) \  
    do \  
    { \  
        if (!(cond)) \  
        { \  
            ReportFailure(#cond, __FILE__, __LINE__, 0); \
            HALT(); \
        } \  
    } while(0)  
#else  
#define ASSERT(cond) \  
    do { (void)sizeof(cond); } while(0) 

While thinking about modifying my code with what I have learned, I noticed a couple of interesting variations posted in the comments for that article:

One was that you cannot use this macro with the ternary operator (i.e. cond?ASSERT(x):func()), and the suggestion was to replace the if() with the ternary operator and some parentheses as well as the comma operator. Later on another commenter provided this:

#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif

I thought the use of logical and && is particularly smart in this case and it seems to me that this version is more flexible than one using the if or even the ternary ?:. Even nicer is that the return value of assert_handler can be used to determine if the program should halt. Although I am not sure why it is (HALT(), 1) instead of just HALT().

Are there any particular shortcomings with the second version here that I have overlooked? It does away with the do{ } while(0) wrapped around the macros but it seems to be unnecessary here because we don't need to deal with ifs.

What do you think?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

不必了 2024-10-28 06:27:10

在 C 和 C++ 标准库中,assert 是充当函数所需的宏。该要求的一部分是用户必须能够在表达式中使用它。例如,使用标准 assert 我可以执行

int sum = (assert(a > 0), a) + (assert(b < 0), b);

与功能相同的操作,

assert(a > 0 && b < 0)
int sum = a + b;

尽管前者可能不是编写表达式的好方法,但该技巧在许多更合适的情况下仍然非常有用。

这立即意味着,如果希望自己的自定义 ASSERT 宏模仿标准 assert 行为和可用性,则可以使用 ifdo { ASSERT 定义中的 while (0) 是没有问题的。一种仅限于这种情况下的表达式,即使用 ?: 运算符或短路逻辑运算符。

当然,如果不关心制作类似标准的自定义 ASSERT,那么可以使用任何东西,包括 if。链接的文章似乎根本没有考虑这个问题,这很奇怪。在我看来,类似函数的断言宏肯定比非类似函数的断言宏更有用。

至于 (HALT(), 1)... 这样做是因为 && 运算符需要有效的参数。 HALT() 的返回值可能不代表 && 的有效参数。据我所知,它可能是 void,这意味着仅仅 HALT() 根本无法编译为 && 的参数>。 (HALT(), 1) 的计算结果始终为 1 且类型为 int,该类型始终是 & 的有效参数。 &。因此,无论 HALT(), 1) 的类型如何,(HALT(), 1) 始终是 && 的有效参数。

您最后关于 do{ } while(0) 的评论似乎没有多大意义。将宏包含在 do{ } while(0) 中的目的是处理外部 if,而不是宏内部的 if定义。您总是必须处理外部if,因为您的宏总是有可能在外部if中使用。在后一个定义中,不需要 do{ } while(0),因为该宏是一个表达式。作为一个表达式,它自然已经不存在外部 if 的问题。所以,没有必要对他们做任何事情。此外,正如我上面所说,将其包含在 do{ } while(0) 中将完全违背其目的,将其变成非表达式。

In C and C++ standard library, assert is a macro that is required to act as a function. A part of that requirement is that users must be able to use it in expressions. For example, with standard assert I can do

int sum = (assert(a > 0), a) + (assert(b < 0), b);

which is functionally the same as

assert(a > 0 && b < 0)
int sum = a + b;

Even though the former might not be a very good way to write an expression, the trick is still very useful in many more appropriate cases.

This immediately means that if one wants their own custom ASSERT macro to mimic standard assert behavior and usability, then using if or do { } while (0) in the definition of ASSERT is out of question. One is limited to expressions in that case, meaning using either ?: operator or short-circuiting logical operators.

Of course, if one doesn't care about making a standard-like custom ASSERT, then one can use anything, including if. The linked article doesn't even seem to consider this issue, which is rather strange. In my opinion, a function-like assert macro is definitely more useful than a non-function-like one.

As for the (HALT(), 1)... It is done that way because && operator requires a valid argument. The return value of HALT() might not represent a valid argument for &&. It could be void for what I know, which means that a mere HALT() simply won't compile as an argument of &&. The (HALT(), 1) always evaluates to 1 and has type int, which is always a valid argument for &&. So, (HALT(), 1) is always a valid argument for && regardless of the type of HALT().

Your last comment about do{ } while(0) does not seem to make much sense. The point of enclosing a macro into do{ } while(0) is to deal with external ifs, not the ifs inside the macro definition. You always have to deal with external ifs, since there's always a chance that your macro will be used in an external if. In the latter definition do{ } while(0) is not needed because that macro is an expression. And being an expression, it already naturally has no problems with external ifs. So, there's no need to do anything about them. Moreover, as I said above, enclosing it into do{ } while(0) would completely defeat its purpose, turning it into a non-expression.

ㄖ落Θ余辉 2024-10-28 06:27:10

为了完整起见,我在 C++ 中发布了一个包含 2 个文件的断言宏实现:

#include <pempek_assert.h>

int main()
{
  float min = 0.0f;
  float max = 1.0f;
  float v = 2.0f;
  PEMPEK_ASSERT(v > min && v < max,
                "invalid value: %f, must be between %f and %f", v, min, max);

  return 0;
}

将提示您:

Assertion 'v > min && v < max' failed (DEBUG)
  in file e.cpp, line 8
  function: int main()
  with message: invalid value: 2.000000, must be between 0.000000 and 1.000000

Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:

其中

  • (I)gnore:忽略当前断言
  • 忽略 (F)orever:记住触发断言的文件和行和
    在程序的剩余执行中忽略它
  • 忽略 (A)ll:忽略所有剩余的断言(所有文件和行)
  • (D)ebug:如果已连接,则中断到调试器,否则 abort() (on视窗,
    系统将提示用户附加调试器)
  • A(b)ort:立即调用 abort()

您可以在此处找到更多信息:

希望有帮助。

For the sake of completeness, I published a drop-in 2 files assert macro implementation in C++:

#include <pempek_assert.h>

int main()
{
  float min = 0.0f;
  float max = 1.0f;
  float v = 2.0f;
  PEMPEK_ASSERT(v > min && v < max,
                "invalid value: %f, must be between %f and %f", v, min, max);

  return 0;
}

Will prompt you with:

Assertion 'v > min && v < max' failed (DEBUG)
  in file e.cpp, line 8
  function: int main()
  with message: invalid value: 2.000000, must be between 0.000000 and 1.000000

Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:

Where

  • (I)gnore: ignore the current assertion
  • Ignore (F)orever: remember the file and line where the assertion fired and
    ignore it for the remaining execution of the program
  • Ignore (A)ll: ignore all remaining assertions (all files and lines)
  • (D)ebug: break into the debugger if attached, otherwise abort() (on Windows,
    the system will prompt the user to attach a debugger)
  • A(b)ort: call abort() immediately

You can find out more about it there:

Hope that helps.

小鸟爱天空丶 2024-10-28 06:27:10

虽然我不确定为什么是 (HALT(), 1) 而不是 HALT()

我想 HALT 可能是 exit 的宏(或其他替代名称)。假设我们想使用 exit(1) 作为 HALT 命令。 exit 返回 void,它不能作为 && 的第二个参数进行计算。如果您使用逗号运算符,它会计算第一个参数,然后计算并返回第二个参数的值,我们将有一个整数 (1) 返回到 &&,即使我们从未达到这一点是因为 HALT() 会让我们在那之前很久就停下来。

基本上,任何填充 HALT 的函数都可能有一个 void 返回值,因为它返回任何值都是没有意义的。我们可以让它返回一个int,只是为了宏,但如果我们已经在使用宏进行黑客攻击,那么再多一点黑客攻击也无伤大雅,可以吗?

Although I am not sure why it is (HALT(), 1) instead of just HALT().

I imagine HALT may be a macro (or other stand-in name) for exit. Say we wanted to use exit(1) for our HALT command. exit returns void, which cannot be evaluated as the second argument to &&. If you use the comma operator, which evaluates its first argument, then evaluates and returns the value of it's second argument, we have an integer (1) to return to &&, even though we never reach that point because HALT() will cause us to stop long before then.

Basically, any function that fills in for HALT is probably going to have a return value of void, because it would make no sense for it to return any value. We could make it return an int, just for the sake of the macro, but if we're already hacking around with a macro a little more hackery can't hurt, can it?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文