宏和后增量

时间:2011-09-15 14:01:58

标签: 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++));
}

输出为4 6 5. b的值增加两次,但在MAX显示其值之前不增加。任何人都可以告诉我为什么会这样,以及如何预测这种行为? (应该避免使用宏的另一个例子!)

8 个答案:

答案 0 :(得分:6)

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

printf("%d %d %d\n",a,b, a++ > b++ ? a++ : b++);

这具有未定义的行为,因为b可能会增加(在第三个参数的末尾),然后使用(在第二个参数中)而没有插入序列点。

但是和任何UB一样,如果你盯着它看一段时间,你可能会想出一个解释你的实现实际上已经做了什么来产生你看到的结果。参数的评估顺序是未指定的,但它看起来好像参数已经按从右到左的顺序进行了评估。首先,ab递增一次。 a不大于b,因此b再次递增,条件表达式的结果为5(也就是说,b之后第一次增量,第二次增加之前。

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

答案 1 :(得分:2)

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

你的例子:

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)

答案 2 :(得分:2)

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

所以你的功能变成了

    printf(a,b,(a++>b++?a++;b++));

此处的评估顺序是“依赖于编译器”。

要了解何时可能发生这些情况,您必须了解序列点。

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

    a[i] = i++;

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

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

1)在评估其参数后调用函数的重点。

2)&&的第一个操作数的结尾操作

3)||的第一个操作数的结束操作

4)?:条件运算符的第一个操作数的结尾。

5)逗号运算符的每个操作数的结尾。

6)完成对完整表达式的评估。它们如下:

评估自动对象的初始化程序。

“普通”语句中的表达式 - 表达式后跟分号。

do,while,if,switch或for statements中的控制表达式。

for语句中的另外两个表达式。

返回语句中的表达式。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

如果我是对的,就会发生这种情况:

用MAX替换为(a> b ...)你有printf(“%d%d%d \ n”,a,b,(a ++> b ++?a ++:b ++));

首先,a ++>检查b ++并且之后两个值都增加(a = 4,b = 5)。 然后第二个b ++变为活动状态,但因为它是后增量,所以在打印第二个值b = 5后它会增加。

抱歉我的英语不好,但我希望你理解它?! :d

来自德国的欢迎; - )

拉​​尔夫

答案 5 :(得分:1)

所以你的扩展给出了(为了清晰度而调整):

(a++ > b++) ? a++ : b++

...因此首先评估(a++ > b++),每个都给出一个增量,并根据ab的尚未增加的值选择分支。选择'else'表达式b++,它在b上执行第二个增量,该增量已在测试表达式中递增。由于它是后增量,因此b在第二次增量之前的值被赋予printf()

答案 6 :(得分:0)


您到达这里的结果有两个原因:

  1. 宏只是在编译时展开和粘贴的代码。所以你的宏

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

    变成这个

    a++>b++?a++:b++
    

    你的结果是:

    printf("%d %d %d\n",a,b, a++>b++?a++:b++);
    

    现在应该清楚为什么b增加两次:首先是比较,第二次是返回。这不是未定义的行为,它定义明确,只需分析代码,您就会看到完全符合预期的行为。 (这在某种程度上是可以预测的)

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

    int main(void)
    {
        int a = 3, b=4;
        printf("%d %d %d\n",a,b, b++);
        return 0;
    }
    
  3. 输出为3 5 4。 我希望这可以解释这种行为并帮助您预测结果。
    ,最后一点取决于您的编译器

答案 7 :(得分:-1)

我认为提问者期待输出开始:

3 4 ...

而不是:

4 6 ...

这是因为参数在被推入堆栈时从右到左进行评估,即最后一个参数被评估并被推送,然后是倒数第二个,然后是第二个参数,最后第一个参数。

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

更新

定义评估顺序,但定义为未定义(感谢Steve)。您的编译器碰巧就是这样做的。我认为我在评估顺序和参数传递顺序之间感到困惑。