定义宏时使用逗号运算符

时间:2019-09-03 19:11:02

标签: c c-preprocessor

考虑到逗号运算符可以适当使用K&R状态的情况(第3章,第63页)“ ...”,并且在多步计算必须为单个表达式的宏中。

现在我知道#define SYMVAR (expr1, expr2, expr3)SYMVAR设置为expr3,但这并不是本书所建议的用途。我的困惑源于缺少使用逗号运算符的上述示例;其次,只有当每个逗号分隔的表达式实际上以任何方式对宏本身的值有所贡献时,宏中的多步计算才是有目的的,只有当我们以某种方式将一些中间值存储在某些临时变量中而不会任何意义,因为我们在谈论预处理器指令。

我想念什么吗?本书所描述的使用逗号运算符的正确示例是什么?

3 个答案:

答案 0 :(得分:3)

想象一个功能

void debug_log(const char *s);

将指定的字符串(连同时间戳)写入某种日志文件。

然后您可以定义以下宏:

#define TWICE(X)  (debug_log("Calling TWICE(" #X ")"), (X) * 2)

TWICE(21)的计算结果为42,但还会写出以下格式的消息

[2019-09-03 12:34:56] Calling TWICE(21)

到日志文件。

关键是C语言中的表达式可能会有副作用。即使不使用返回值,他们也可以修改变量,可以写入文件等。

答案 1 :(得分:1)

melpomene提供了一个示例。另一个示例,尽管您可能会争论它是好是坏,但是是否要在循环头中使用该宏。

#define MACRO(X) (X--, X>0)

int x=5;
while(MACRO(x)) {
    // Do stuff
}

此示例绝对不是世界上最好的示例,但要点是,如果使用分号代替表达式,无论如何封装它们,它都将无法工作。

答案 2 :(得分:1)

由于逗号运算符语句的值是语句中最后一个用逗号分隔的表达式,因此您可以使用它执行多个表达式,同时仅保留最后一个表达式的值。

例如,假设您想要一个宏,它将执行多个计算,但是该宏的值是最后一个计算。以下宏利用了表达式中使用的赋值运算符来执行计算,并允许将不同的计算作为宏的结果:

#define SYMVAR(a,b)  ( ((a)=(b)), (b)*2)

这也可以是一系列函数调用,例如:

int f1(int x) { return x * 3; }

int f2(int x) { return x * 4; }

// call first function f1() and then f2() with the value of the macro being the
// value returned by function f2().
#define SYMVARy(a) (f1(a), f2(a))

使用此方法的示例:

#include <stdio.h>

#define SYMVAR(a,b)  ( ((a)=(b)), (b)*2)

#define SYMVAR2(a,b,z)  ( ((a)=(b)), ((z)=(b))*2)

int main()
{
    int n1, n2;
    int x1, x2;
    int y0;
    int i;

    for (i = 0; i < 10; i++) {
        printf(" i = %d  -> ", i);
        x1 = SYMVAR(n1, i);
        printf(" n1 = %d, i = %d, x1 = %d\n", n1, i, x1);
    }

    printf("\nloop 2\n");
    for (i = 0; i < 10; i++) {
        printf(" i = %d  -> ", i);
        x2 = SYMVAR2(n2, i+3, y0);
        printf(" n2 = %d, i = %d, x2 = %d, y0 = %d\n", n2, i, x2, y0);
    }

    return 0;
}

该程序的输出为:

 i = 0  ->  n1 = 0, i = 0, x1 = 0
 i = 1  ->  n1 = 1, i = 1, x1 = 2
 i = 2  ->  n1 = 2, i = 2, x1 = 4
 i = 3  ->  n1 = 3, i = 3, x1 = 6
 i = 4  ->  n1 = 4, i = 4, x1 = 8
 i = 5  ->  n1 = 5, i = 5, x1 = 10
 i = 6  ->  n1 = 6, i = 6, x1 = 12
 i = 7  ->  n1 = 7, i = 7, x1 = 14
 i = 8  ->  n1 = 8, i = 8, x1 = 16
 i = 9  ->  n1 = 9, i = 9, x1 = 18

loop 2
 i = 0  ->  n2 = 3, i = 0, x2 = 6, y0 = 3
 i = 1  ->  n2 = 4, i = 1, x2 = 8, y0 = 4
 i = 2  ->  n2 = 5, i = 2, x2 = 10, y0 = 5
 i = 3  ->  n2 = 6, i = 3, x2 = 12, y0 = 6
 i = 4  ->  n2 = 7, i = 4, x2 = 14, y0 = 7
 i = 5  ->  n2 = 8, i = 5, x2 = 16, y0 = 8
 i = 6  ->  n2 = 9, i = 6, x2 = 18, y0 = 9
 i = 7  ->  n2 = 10, i = 7, x2 = 20, y0 = 10
 i = 8  ->  n2 = 11, i = 8, x2 = 22, y0 = 11
 i = 9  ->  n2 = 12, i = 9, x2 = 24, y0 = 12

此方法的局限性

此方法的局限性在于无法创建范围为逗号表达式本身的临时变量。

一种解决方法是使用do { … } while(0)构造,但是您会遇到宏在分配中不再有效的问题。

其他一些注意事项

使用这种方法可能有些棘手。您必须始终记住,C预处理程序正在执行文本替换,并且对此没有很多技巧。

您应该使用括号强制执行特定的求值顺序,并确保使用宏时提供的替换文本将导致正确的操作顺序((i + 3) * 2而非i + 3 * 2由于运算符的优先顺序,导致两个不同的语句。

还要注意,在一个宏指令中多次使用宏参数的宏中使用具有副作用的表达式可能会导致意想不到的后果。例如:

#define SYMVAR(a,b)  ( ((a)=(b)), (b)*2)

int xA, xB = 2, xVal;

xVal = SYMVAR(xA, xB++);    // macro argument has a side effect, post increment

将导致xVal的值为6(xB自发布后增加一次,然后乘以2),xA的值为2(原始值为xB(因为使用后递增)和xB的值为4(xB被递增两次)。

您可以使用上述宏之一,例如:

xx SYMVAR(n1, n2);

,它会编译为警告,然后在链接期间失败。在Visual Studio 2017中,将上述语句添加到上述程序中时会看到以下警告和错误。请注意,这些警告和错误都是与缺少外部函数xx有关,而不是您可能期望的编译器错误。

Severity    Code    Description Project File    Line    Suppression State
Warning C4013   'xx' undefined; assuming extern returning int   console_scan    d:\users\rickc\documents\vs2017repos\console_scan\console_scan\source_macro.c   27  
Error   LNK2019 unresolved external symbol _xx referenced in function _main console_scan    D:\Users\rickc\Documents\vs2017repos\console_scan\console_scan\Source_Macro.obj 1   
Error   LNK1120 1 unresolved externals  console_scan    D:\Users\rickc\Documents\vs2017repos\console_scan\Debug\console_scan.exe    1