11701:宏的重复求值
题目
分析 MAX(a, b) 宏对带副作用实参的展开结果,并用类型明确的内联函数替代它。
解析
宏展开只替换记号,不会像函数那样先把每个实参求值一次。调用:
c
MAX(i++, j++)1
会展开为:
c
((i++) > (j++) ? (i++) : (j++))1
条件部分先求值,随后只求值第二或第三操作数中的一个。因此某一对象会递增一次,较大一侧对应的对象会再递增一次。
当初值为 i = 3, j = 5 时,条件为假。条件求值后得到 i = 4, j = 6,随后 j++ 产生结果 6,并使 j 变为 7。最终结果为 6,状态为 i = 4, j = 7。
当初值为 i = 5, j = 3 时,条件为真。条件求值后得到 i = 6, j = 4,随后 i++ 产生结果 6,并使 i 变为 7。最终结果为 6,状态为 i = 7, j = 4。
解析
c
static inline int max_int(int left, int right) {
return left > right ? left : right;
}1
2
3
2
3
为什么内联函数更合适
调用 max_int(i++, j++) 时,每个实参只求值一次。函数是否真正内联由实现决定,不影响这一语义。
两个函数实参的求值次序仍未指定。因此 max_int(i++, i++) 仍然在彼此没有顺序保证的情况下多次修改同一个标量对象,会产生未定义行为。内联函数消除了函数体中的重复求值,但不能修复调用表达式本身的非法副作用组合。
给宏形参加括号只能解决运算符结合方式问题,不能解决求值次数问题。需要支持其他类型时,应为所需类型分别提供明确的内联函数,或者使用后续章节介绍的受控泛型分派。