访问宏中的变量值

Accessing variable values within a macro

提问人:Azeirah 提问时间:9/25/2015 最后编辑:M.MAzeirah 更新时间:7/13/2016 访问量:1503

问:

前段时间,我为 c 和 c++ 程序制作了这个漂亮的断言宏

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

在整个代码中分散 ASSERT 调用,每当值不真实时,它就会警告您!在开发过程中非常方便,可以提醒您潜在的错误。truthy

前任

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

当 filesFound 为 0 时,宏将打印出来

找不到任何文件,请检查您的路径!在文件中的第 27 行 openFiles.c. 检查文件是否找到> 0

现在,我希望它打印出来,为我提供更多相关信息,是传递到参数中的任何变量的值。喜欢这个truthy

找不到任何文件,请检查您的路径!在文件中的第 27 行 openFiles.c. 检查 filesFound > 0,filesFound 是否为 0

这似乎是类似 lisp 的领域,我想知道,是否有任何黑魔法 c 预处理可用于将变量和函数评估到它们的值,而无需评估语句?truthy

我想很失望。

C++

评论

0赞 Kninnug 9/25/2015
标准过程是将宏包装在语句中,然后可以使用 - 的本地变量。请注意,该标准通常也会打印相关信息。一个常见的技巧是打印带有表达式的消息。do{ ... }while(0);dowhileassertassert(truthy && "The foo was barred!");
1赞 Konrad Rudolph 9/25/2015
@Kninnug 使用的真正原因是强制/启用宏的用户在宏调用后放置分号。局部变量也可以在 OP 的宏中工作。do {…} while (false)
1赞 Konrad Rudolph 9/25/2015
不要打扰。这非常复杂。如果你真的、真的、想实现它,看看 Catch 是如何做到的。
0赞 Kninnug 9/25/2015
@KonradRudolph我的意思是以这样的形式,在宏之外不可见,并且不会干扰其他局部变量。do{ int val = ... }while(0);val
0赞 Konrad Rudolph 9/25/2015
@Kninnug我知道。但你在这里不需要它,在正文范围内的 OP 代码中也是如此。do…whileif

答:

0赞 Serge Ballesta 9/25/2015 #1

我想不出有什么办法可以做到这一点......除非通过传递另一个参数

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

你可以这样使用它:

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

得到:

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

评论

1赞 rkachach 9/25/2015
我认为他是在问如何打印出任何表达式中的所有术语,在这种情况下,您的解决方案不起作用。
1赞 Serge Ballesta 9/25/2015
@redobot:你说得对。但这是一个快速而强大的解决方法。借助 ASSERT_PARAM、ASSERT_PARAM2、ASSERT_PARAM3和ASSERT_PARAM4,您应该涵盖 99% 的用例。我总是希望测试和诊断代码尽可能简单,除非它来自一个强大的可靠来源(我认为我不是......
0赞 rkachach 9/25/2015 #2

你要做的事情听起来非常复杂。恐怕在 C++ 中这是不可能的。

从技术上讲,您正在评估的是一个布尔表达式,因此您可以在断言失败时将其传递给解析器。然后,解析器将构建表达式树,获取叶子(表达式的元素)并返回它们。然后,应打印出返回的值。为此,您将需要对反射的支持,这在 C++ AFAIK 中实际上不受支持。

评论

0赞 Konrad Rudolph 9/25/2015
这是可能的——Catch 库做到了。但这非常困难。
0赞 rkachach 9/25/2015
AFAIK 标准 C++ 没有。您可以看看该决定背后的原因:stackoverflow.com/questions/359237/......
1赞 Konrad Rudolph 9/25/2015
再读一遍我的评论:Catch 库做到了。在标准 C++ 中。
0赞 user1196549 9/25/2015 #3

也许不是梦寐以求的解决方案,但您可以将整个语句传递给宏。

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

ASSERT(trusty, cout << a << b;)
ASSERT(trusty, printf("%d, %f\n", a, b);)
0赞 Thomas Sparber 9/25/2015 #4

我认为您可以像他们在这里的第一个答案中所做的那样拆分表达式,然后您可以打印单个值。但我不确定它是否真的有效。truthy

然后可以使用可变参数模板功能对打印进行重新调整

0赞 Lundin 9/25/2015 #5

也许您可以妥协,在断言表达式中只允许 2 个变量和 1 个运算符?如果是这样,您可以像这样制定临时解决方案:

#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;
}

输出:

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.
0赞 Kusalananda 6/22/2016 #6

我想知道让宏接收消息是否真的那么有用。失败的断言是向开发人员发送的消息,表明代码中存在导致异常行为或使程序处于不可接受的状态的 bug。用户与它的关系较小(如果他们甚至可以访问源代码)。

下面的代码定义了一个宏,该宏采用布尔表达式,对其进行计算并打印信息性消息。该消息包含一个值,您要求在断言失败时检查该值。ASSERT

该宏,就像标准宏 (in ) 继续调用 (from ) 以导致异常程序终止一样。这就是你想要的,因为程序进入了一种状态,在这种状态下,它不知道该做什么。assert()<cassert>abort()<cstdlib>

为了简洁起见,我在这里使用。你想做什么就做什么。std::printf()

#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());
    //...
}

程序运行:

$ ./a.out
ASSERTION FAILED: 'foo() - 40 == 1', foo() is 42: [email protected]:16
Abort

如果不向宏添加更多参数,就不可能完全按照您的要求执行操作。在某些时候,你必须停下来,意识到你正在花时间创建一个你不想看到的文本字符串。

0赞 tony 7/10/2016 #7

您需要构建一个表达式“grabber”/builder。

宏将变成如下内容:

#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; \
 }

Grabber 是做什么的?

这是一堆疯狂的 C++ 构建了一个表达式。它会使每个运算符过载以“抓取”运算符的参数。每个运算符都返回对抓取器的引用,以便它可以抓取下一个运算符。即

Grabber g;
g % filesFound > 0;

由于 %(以及 * 和 /)具有很高的优先级,因此上面的解析如下:

((g % filesFound) > 0)

如果只是记录(或打印)传入的值(即 filesFound),并且 - 重要的是 - 返回自身 (g),以便它成为下一个表达式的一部分:即它变为 g > 0。导致被调用,> 0 被记录。template<typename T> Grabber::operator%(T const & val)template<typename T> Grabber::operator>(T const & val)

然后可以喷出所有抓住的东西。cout << g

如上所述,“这是可能的——Catch 库做到了。但这非常困难”。

P.S. 你应该把你的宏包装在一个 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)

您目前所拥有的意味着这是有效代码:

ASSERT(foo != 0)
else
{
}

这不是有效的代码:

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

评论

0赞 Jonathan Leffler 7/10/2016
这条线是如何工作的?难道不应该用括号括起来,以便如果你得到而不是?我假设运算符过载。g % truthy;truthytruthyfilesFound > 0g % (filesFound > 0)(g % filesFound) > 0Grabber%
0赞 tony 9/3/2016
在大多数宏中,是的,你想用括号括起来,但不在这里 - 这会让它不起作用,奇怪的是。 意味着首先被评估 - 但你不希望这样 - 你想抓住表达式的每一部分,而不仅仅是表达式的结果。g % (filesFound > 0)filesFound > 0g
0赞 Kingsley Chen 7/10/2016 #8

令人惊讶的是,我之前解决过类似的问题,但我不确定在这种情况下它是否能帮助你。

最初的解决方案是由安德烈·亚历山德莱斯库(Andrei Alexandrescu)在《增强断言》一文中提出的,毫无疑问,它依赖于一些宏观技巧。

这个惊人的设施可以用作以下用途:

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

如果出现问题,将显示该消息

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

需要注意的是,从理论上讲,SMART_ASSERT可以捕获无限的变量。

有关实现细节,请查看文章。

1赞 Mark 7/13/2016 #9

我一直使用的另一种解决方案是在宏中支持 varargs,然后强制断言用户指定相关的消息/变量 - 每次都需要一些额外的工作,但从好的方面来说,您可以获得您想要的格式,并包含“真实”位中不可用的信息, 例如:

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

那么你的处理程序只是一个相当标准的 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.
}

用法:

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