简体   繁体   English

访问宏中的变量值

[英]Accessing variable values within a macro

Some time ago, I made this beautiful assert macro for c and c++ programs 前段时间,我为c和c ++程序制作了这个漂亮的断言宏

#define ASSERT(truthy, message) \
     if (!(truthy)) \
     {\
         cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy << endl;\
     }

Scatter ASSERT calls throughout your code, and it will warn you whenever the truthy value is not truthy! Scatter ASSERT会在整个代码中调用它,并且只要truthy值不真实,它就会发出警告! Very handy during development to remind you of potential mistakes. 在开发过程中非常方便,以提醒您潜在的错误。

ex

ASSERT(filesFound > 0, "Couldn't find any files, check your path!");

When filesFound is 0, the macro will print out 当filesFound为0时,宏将打印出来

Couldn't find any files, check your path! 找不到任何文件,检查你的路径! on line 27 in file openFiles.c. 在文件openFiles.c的第27行。 Check was filesFound > 0 检查是filesFound> 0

Now what I want it to print, to give me even more relevant information, is the value of any variables passed into the truthy parameter. 现在我想要它打印,给我更多相关信息,是传递到truthy参数的任何变量的值。 Like this 像这样

Couldn't find any files, check your path! 找不到任何文件,检查你的路径! on line 27 in file openFiles.c. 在文件openFiles.c的第27行。 Check was filesFound > 0, filesFound is 0 检查是filesFound> 0,filesFound是0

This seems lisp-like territory, I wonder, is there any black magic c preprocessing that I can use to evaluate variables and functions to their values, without evaluating the truthy statement? 我想知道,这似乎是类似lisp的领域,是否有任何黑魔法c预处理可用于评估变量和函数的值,而不评估truthy声明?

I assume to be disappointed. 我假设很失望。

An alternative solution which I've always used is to support varargs in the macro and then force the assert user to specify the relevant message / variables - it's a little bit of extra work each time, but on the plus side you can get exactly the formatting that you want and include information not available in the "truthy" bit, eg: 我一直使用的另一种解决方案是在宏中支持varargs,然后强制断言用户指定相关的消息/变量 - 每次都是一些额外的工作,但从好的方面来说,你可以完全得到您想要的格式,并包含“truthy”位中没有的信息,例如:

#define ASSERT(truthy, message, ...) \
if (!(truthy)) \
{\
    MyAssertHandler(__LINE__, __FILE__, #truthy, message, ##__VA_ARGS__);
}

Then you're handler is just a fairly standard var-arg function that can use eg vsnprintf to generate the message and output it, eg off the top of my head: 那么你的处理程序只是一个相当标准的var-arg函数,它可以使用例如vsnprintf生成消息并输出它,例如我的头顶:

void MyAssertHandler(int line, const char* file, const char* expressionStr, const char* format, ...)
{
    // Note: You probably want to use vsnprintf instead to first generate
    //       the message and then add extra info (line, filename, etc.) to
    //       the actual output 
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);

    // Log to bug database, DebugBreak() if a debugger is attached, etc.
}

usage: 用法:

ASSERT(IsBlah(), "BlahBlah: x = %.2f, name = %s", GetX(), GetName());

I cannot imagine a way to do it... except by passing another parameter 我无法想象这样做的方法......除了传递另一个参数

#define ASSERT_PARAM(truthy, message, param) \
     if (!(truthy)) \
     {\
         cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy  << ", value was " << param << endl;\
     }

You would use it that way: 你会这样使用它:

ASSERT_PARAM(filesFound > 0, "Couldn't find any files, check your path!", filesFound);

getting: 得到:

Couldn't find any files, check your path! on line 27 in file openFiles.c. Check was filesFound > 0, value was 0

What you are trying to do sounds very complicated. 你想要做的事听起来非常复杂。 I'm afraid in C++ it's not possible. 我担心在C ++中它是不可能的。

Technically what you are evaluating is a bool expression so you can pass it to a parser whenever the assertion fails. 从技术上讲,您正在评估的是bool表达式,因此只要断言失败,您就可以将其传递给解析器。 The parser then will build the expression tree, get the leaves (elements of the expression) and return them. 解析器然后将构建表达式树,获取叶子(表达式的元素)并返回它们。 The returned values then should be printed out. 然后应打印出返回的值。 To do that you will need support for reflection which is actually not supported in C++ AFAIK. 为此,您需要支持C ++ AFAIK实际上不支持的反射。

Maybe not the dream solution, but you can pass whole statements to a macro. 也许不是梦想的解决方案,但您可以将整个语句传递给宏。

#define ASSERT(trusty, action) if (!trusty) { action }

ASSERT(trusty, cout << a << b;)
ASSERT(trusty, printf("%d, %f\n", a, b);)

I think you can split up the truthy Expression like they do it in the first answer here and then you can probably print the individual values. 我想你可以分裂的truthy像他们这样做的第一个答案表达在这里 ,然后你大概可以打印个人价值。 But I'm not sure if it actually works. 但我不确定它是否确实有效。

The printing could then be resulved using a variadic template function 然后可以使用可变参数模板函数来进行打印

Perhaps you could compromise and only allow 2 variables and 1 operator in the assertion expression? 也许你可以妥协,只允许在断言表达式中使用2个变量和1个运算符? If so, you could make an ad hoc solution like this: 如果是这样,您可以制作这样的临时解决方案:

#include <iostream>
#include <string>

#define STRINGIFY(x) #x

#define BIN_ASSERT(obj1, op, obj2, msg)                                 \
  if(!(obj1 op obj2))                                                   \
  {                                                                     \
    std::cout << msg << " on line " << __LINE__                         \
         << " in file " << __FILE__                                     \
         << "." << std::endl                                            \
         << "Check was "                                                \
         << STRINGIFY(obj1) STRINGIFY(op) STRINGIFY(obj2)               \
         << "." << std::endl                                            \
         << "Operator " << #obj1 << ": " << obj1                        \
         << "." << std::endl                                            \
         << "Operator " << #obj2 << ": " << obj2                        \
         << "." << std::endl;                                           \
  }


int main (void)
{
  int x = 2;
  int y = 3;
  std::string s1 = "hello";
  std::string s2 = "world";

  BIN_ASSERT(1, +, -1, "Value zero"); std::cout << std::endl;
  BIN_ASSERT(x, ==, y, "Numbers not equal"); std::cout << std::endl;
  BIN_ASSERT(s1, ==, s2, "Strings not equal"); std::cout << std::endl;
}

Output: 输出:

Value zero on line 30 in file test.c.
Check was 1+-1.
Operator 1: 1.
Operator -1: -1.

Numbers not equal on line 31 in file test.c.
Check was x==y.
Operator x: 2.
Operator y: 3.

Strings not equal on line 32 in file test.c.
Check was s1==s2.
Operator s1: hello.
Operator s2: world.

I wonder if having the macro take a message is really that useful. 我想知道宏是否接收消息真的很有用。 A failed assertion is a message to the developer that there is a bug in the code that caused an exceptional behaviour or put the program in an unacceptable state. 失败的断言是向开发人员发出的一条消息,即代码中存在导致异常行为或将程序置于不可接受状态的错误。 The user has less to do with it (if they even have access to the source code). 用户与它的关系较少(如果他们甚至可以访问源代码)。

The code below defines an ASSERT macro that takes a boolean expression, evaluates it and prints an informational message. 下面的代码定义了一个ASSERT宏,它接受一个布尔表达式,对其进行求值并输出一条信息性消息。 The message contains a value that you've asked to inspect upon failing the assertion. 该消息包含您在断言失败时要求检查的值。

The macro, just like the standard assert() macro (in <cassert> ) goes on to call abort() (from <cstdlib> ) to cause an abnormal program termination. 宏,就像标准的assert()宏(在<cassert> )继续调用abort() (来自<cstdlib> )来导致程序异常终止。 This is what you want, because the program entered a state in which it didn't know what more to do. 这就是你想要的,因为程序进入了一个不知道还要做什么的状态。

I'm using std::printf() here for brevity. 我在这里使用std::printf()为了简洁。 You do whatever you want. 你做任何你想做的事。

#include <cstdlib>
#include <cstdio>

#define ASSERT(value, inspect)                                                 \
    if (!(value)) {                                                            \
        std::printf("ASSERTION FAILED: '%s', %s is %d: %s@%s:%d\n", #value,    \
                    #inspect, inspect, __func__, __FILE__, __LINE__);          \
        abort();                                                               \
    }

int foo() { return 42; }

int main()
{
    // ...
    ASSERT(foo() - 40 == 1, foo());
    //...
}

Program run: 程序运行:

$ ./a.out
ASSERTION FAILED: 'foo() - 40 == 1', foo() is 42: main@prog.cc:16
Abort

It's not possible to do exactly what you ask for without adding more parameters to the macro. 如果不向宏添加更多参数,则无法完全按照您的要求执行操作。 At some point you'll have to stop and realize that you're spending time on creating a text string that you do not want to see . 在某些时候,你必须停下来意识到你花时间创建一个你不想看到的文本字符串。

You need to build an expression 'grabber' / builder. 您需要构建表达式“grabber”/ builder。

The macro would become something like: 宏将变成如下:

#define ASSERT_PARAM(truthy, message, param) \
 if (!(truthy)) \
 {\
     Grabber g;
     g << #truthy; // grab expression as string
     g % truthy;  // grab expression and values
     cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy  << ", value was " << param << endl;\
     cout << g; \
 }

What does Grabber do? Grabber做什么?

It is a bunch of crazy C++ that builds up an expression. 这是一堆疯狂的C ++,它构建了一个表达式。 It would overload every operator to 'grab' the params of the operator. 它会使每个操作员超载以“抓住”操作员的参数。 Every operator returns a reference to the grabber, so it can grab the next operator. 每个操作员都返回一个对抓取器的引用,因此它可以抓住下一个操作符。 ie

Grabber g;
g % filesFound > 0;

Since % (and * and /) have high precedence, the above parses like: 由于%(和*和/)具有高优先级,因此上述解析如下:

((g % filesFound) > 0)

If template<typename T> Grabber::operator%(T const & val) just records (or prints) the value passed in (ie filesFound), and - importantly - returns itself (g) so that it becomes part of the next expression: ie it becomes g > 0. Causing template<typename T> Grabber::operator>(T const & val) to be called, and > 0 to be recorded. 如果template<typename T> Grabber::operator%(T const & val)只记录(或打印)传入的值(即filesFound),并且 - 重要的是 - 返回自身(g)以使其成为下一个表达式的一部分:即它变为g> 0.导致template<typename T> Grabber::operator>(T const & val)被调用,并且> 0被记录。

Then cout << g can spew out everything grabbed. 然后cout << g可以把所有被抓住的东西吐出来。

As mentioned above "It is possible — the Catch library does it. But it's hellishly difficult". 如上所述“有可能 - Catch库可以做到这一点。但它真是太难了”。

PS you should wrap your macro in a do ... while 0 like this: PS你应该把你的宏包裹在一个do ...而0这样:

#define ASSERT_PARAM(truthy, message, param) \
 do \
 { \
   if (!(truthy)) \
   {\
     cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy  << ", value was " << param << endl;\
     cout << g; \
   } \
 } while (0)

What you have currently means that this is valid code: 你目前所拥有的意味着这是有效的代码:

ASSERT(foo != 0)
else
{
}

And this is NOT valid code: 这不是有效的代码:

if (foo != nullptr)
   ASSERT(foo->bar != nullptr);
else
   x = 10;

Surprisingly, I solved a similar problem before, but I'm not sure if it could help you in this case. 令人惊讶的是,我之前解决了类似的问题,但我不确定它是否可以在这种情况下帮助你。

The original solution was proposed by Andrei Alexandrescu in the article Enhancing Assertions , and with no question, relying on some macro tricks. 最初的解决方案是Andrei Alexandrescu在文章“ 增强断言”中提出的,毫无疑问,它依赖于一些宏观技巧。

This amazing facility can be used as the following: 这个惊人的设施可以用作以下内容:

string s1, s2;
...
SMART_ASSERT(s1.empty() && s2.empty())(s1)(s2);

And if something goes wrong, the message would be displayed 如果出现问题,将显示该消息

Assertion failed in matrix.cpp: 879412:
Expression: 's1.empty() && s2.empty()'
Values: s1 = "Wake up, Neo"
        s2 = "It's time to reload."

Be noted that, the SMART_ASSERT can capture infinite variables, theoretically. 需要注意的是,SMART_ASSERT理论上可以捕获无限变量。

For implementation details, please check out the article. 有关实施细节,请查看文章。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM