C 宏和后增量

C 宏和后增量,c,macros,undefined-behavior,C,Macros,Undefined Behavior,下面是一些更奇怪的宏观行为,我希望有人能解释一下: #define MAX(a,b) (a>b?a:b) void main(void) { int a = 3, b=4; printf("%d %d %d\n",a,b,MAX(a++,b++)); } 输出为465。b的值会增加两次,但不会在MAX显示其值之前。有谁能告诉我为什么会发生这种情况,以及如何预测这种行为?(为什么应该避免使用宏的另一个例子!)在宏中,参数只是被参数替换;因此,如果参数在宏中多次出现,则可以多次求

下面是一些更奇怪的宏观行为,我希望有人能解释一下:

#define MAX(a,b) (a>b?a:b)

void main(void)
{
  int a = 3, b=4;

  printf("%d %d %d\n",a,b,MAX(a++,b++));
}

输出为465。b的值会增加两次,但不会在MAX显示其值之前。有谁能告诉我为什么会发生这种情况,以及如何预测这种行为?(为什么应该避免使用宏的另一个例子!)

在宏中,参数只是被参数替换;因此,如果参数在宏中多次出现,则可以多次求值

你的例子是:

MAX(a++,b++)
扩展到:

a++>b++?a++:b++
我想你不需要更多的解释:)

可以通过将每个参数指定给临时变量来防止出现这种情况:

#define MAX(a,b) ({   \
    typeof(a) _a = a; \
    typeof(b) _b = b; \
    a > b ? a : b;    \
})
(不过,这一个使用了几个GCC扩展)

或使用内联函数:

int MAX(int a, int b) {
    return a > b ? a : b;
}
这将与运行时的宏一样好

或者不执行宏参数中的增量:

a++;
b++;
MAX(a, b)

宏由预处理器计算,它愚蠢地根据宏定义替换所有宏。在您的情况下,
MAX(a++,b++)
变为
(a++>b++)?a++:b++

宏执行文本替换。您的代码相当于:

printf("%d %d %d\n",a,b, a++ > b++ ? a++ : b++);
这具有未定义的行为,因为
b
可能会递增(在第三个参数的末尾),然后在没有插入序列点的情况下使用(在第二个参数中)

但是,与任何UB一样,如果您盯着它看一段时间,您可能会想出一个解释,说明您的实现实际上做了什么,以产生您看到的结果。参数的求值顺序未指定,但在我看来,参数的求值顺序似乎是从右向左的。因此,首先,
a
b
增加一次
a
不大于
b
,因此
b
再次递增,条件表达式的结果是
5
(也就是说,
b
,在第一次递增之后第二次之前)


此行为不可靠-另一个实现或另一天的同一个实现可能由于以不同的顺序评估参数而给出不同的结果,或者理论上甚至可能由于序列点问题而崩溃。

我认为提问者希望输出开始:

3 4 ...
而不是:

4 6 ...
这是因为参数在被推到堆栈上时是从右到左求值的,也就是说,最后一个参数先求值然后被推,然后是从第二个到最后一个,然后是第二个参数,最后是第一个参数

我认为(如果这是错误的,有人会发表评论)这是在C标准(和C++)中定义的

更新


评估的顺序已定义,但定义为未定义(感谢Steve)。你的编译器恰好就是这样做的。我想我混淆了评估顺序和参数传递的顺序。

如果我是正确的,这种情况正在发生:

MAX替换为(a>b…)时,您有printf(“%d%d%d\n”,a,b,(a++>b++?a++:b++)

首先,检查a++>b++,然后增加两个值(a=4,b=5)。 然后第二个b++被激活,但因为它是后增量,所以在打印第二个值b=5后,它会增加

对不起,我的英语不好,但我希望你能理解它?!:D

来自德国的问候;-)

Ralf

因此您的扩展提供(为清晰起见进行了调整):

。。。因此,首先计算
(a++>b++)
,分别给出一个增量,并根据
a
b
的未增量值选择一个分支。选择“else”表达式,
b++
,它在
b
上进行第二次增量,该增量已在测试表达式中递增。由于是后增量,第二个增量之前的
b
值被赋予
printf()


有两个原因导致你在这里得到这样的结果:

  • 宏只不过是编译时展开和粘贴的代码。所以你的宏

    MAX(a,b) (a>b?a:b)
    
    变成这样

    a++>b++?a++:b++
    
    结果函数是:

    printf("%d %d %d\n",a,b, a++>b++?a++:b++);
    
    现在应该清楚为什么b会增加两次:第一次是在比较中,第二次是在返回它时。这不是未定义的行为,而是定义良好的行为,只要分析代码,您就会看到预期的结果。(这在某种程度上是可以预测的)


  • 这里的第二个问题是,在C中,参数是从最后一个传递到第一个到堆栈的,因此在打印a和b的原始值之前,所有的增量都将完成,即使它们列在第一位。试试这行代码,你就会明白我的意思:

  • 输出将为3 5 4。
    我希望这能解释这种行为,并帮助您预测结果
    但是最后一点取决于编译器

    当预处理器读取它将printf中的MAX(a++,b++)替换为(a++>b++?a++;b++)的行时

    所以你的函数变成了

        printf(a,b,(a++>b++?a++;b++));
    
    这里的求值顺序是“依赖于编译器”

    要了解这些情况何时发生,你必须了解序列点

    在每个序列点,将完成前面所有表达式的副作用(将完成所有变量计算)。这就是为什么您不能依赖以下表达式:

        a[i] = i++;
    
    因为没有为赋值、增量或索引操作符指定序列点,所以您不知道增量对i的影响何时发生。 “在上一个序列点和下一个序列点之间,一个对象的存储值最多应通过表达式的计算修改一次。此外,先前的值应为只读,以确定要存储的值。”。如果程序违反了这些规则,任何特定实现的结果都是完全不可预测的(未定义的)

    --本标准中规定的顺序点如下:

    1) 在evaluat之后调用函数的点
        printf(a,b,(a++>b++?a++;b++));
    
        a[i] = i++;