宏和后增量
这是一些奇怪的宏观行为,我希望有人可以阐明:
#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显示其值之前不增加。 任何人都可以告诉我为什么会这样,以及如何预测这种行为? (应避免使用宏的另一个例子!)
宏进行文本替换。 您的代码相当于:
printf("%d %d %d\n",a,b, a++ > b++ ? a++ : b++);
这具有未定义的行为,因为b
可能会增加(在第三个参数的末尾),然后在没有插入序列点的情况下使用(在第二个参数中)。
但是和任何UB一样,如果你盯着它看一段时间,你可能会想出你的实现已经实现了什么来解释你看到的结果。 参数的评估顺序是未指定的,但它看起来好像参数已经按从右到左的顺序进行了评估。 首先, a
和b
递增一次。 a
不大于b
,因此b
再次递增,并且条件表达式的结果为5
(也就是说,在第一个增量之后和第二个增量之前的b
)。
这种行为是不可靠的 – 另一个实现或另一天的相同实现可能会因为以不同的顺序评估参数而给出不同的结果,或者理论上甚至可能因序列点问题而崩溃。
在宏中,参数只是被参数替换; 因此,如果在宏中多次出现参数,则可以多次计算它们。
你的例子:
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)
当预处理器读取行时,它将printf中的MAX(a ++,b ++)替换为(a ++> b ++?a ++; b ++)
所以你的function变成了
printf(a,b,(a++>b++?a++;b++));
评估顺序是“依赖于编译器”。
要了解何时可能发生这些情况,您必须了解序列点。
在每个序列点,将完成所有先前表达式的副作用(所有变量计算将完成)。 这就是为什么你不能依赖于以下表达式的原因:
a[i] = i++;
因为没有为赋值,增量或索引运算符指定序列点,所以不知道增量对i的影响何时发生。 “在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。 此外,先前的值应该是只读的,以确定要存储的值。“ 如果程序违反了这些规则,则任何特定实现的结果都是完全不可预测的(未定义)。
– 标准中规定的顺序点如下:
1)在评估其参数之后调用函数的要点。
2)&&运算符的第一个操作数的结尾。
3)||的第一个操作数的结束 运营商。
4)?:条件运算符的第一个操作数的结束。
5)逗号运算符的每个操作数的结尾。
6)完成对完整表达的评估。 它们如下:
评估自动对象的初始化程序。
表达式为“普通”语句 – 表达式后跟分号。
do,while,if,switch或for语句中的控制表达式。
for语句中的另外两个表达式。
返回语句中的表达式。
宏由预处理器评估,根据宏定义愚蠢地替换所有宏。 在你的情况下, MAX(a++, b++)
变成(a++>b++) ? a++ : b++
(a++>b++) ? a++ : b++
。
如果我是对的,这种情况正在发生:
用MAX替换为(a> b …)你有printf(“%d%d%d \ n”,a,b,(a ++> b ++?a ++:b ++));
首先,检查a ++> b ++,之后两个值都增加(a = 4,b = 5)。 然后第二个b ++变为活动状态,但因为它是后增量,所以在打印第二个值b = 5之后它会增加。
抱歉我的英语不好,但我希望你理解它?! :d
来自德国的比赛;-)
拉尔夫
所以你的扩展给出了(调整清晰度):
(a++ > b++) ? a++ : b++
……首先评估(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的原始值之前完成,即使它们首先列出。 尝试这行代码,你就会理解我的意思:
int main(void) { int a = 3, b=4; printf("%d %d %d\n",a,b, b++); return 0; }
输出将是3 5 4.我希望这可以解释行为并帮助您预测结果。
但是 ,最后一点取决于您的编译器
我认为提问者期待输出开始:
3 4 ...
代替:
4 6 ...
这是因为参数在被推入堆栈时从右到左进行评估,也就是说,最后一个参数被评估并被推送,然后是倒数第二个参数,然后是第二个参数,最后是第一个参数。
我认为(有人发表评论,如果它是错的)这是在C标准(和C ++)中定义的。
更新
评估顺序已定义,但定义为未定义(感谢Steve)。 你的编译器就是这样做的。 我认为我在评估顺序和参数传递顺序之间感到困惑。