这是C / C ++中的未定义行为(第2部分)

时间:2011-03-22 11:11:58

标签: c++ c undefined-behavior

有关序列点的规则对以下代码有何规定?

int main(void) {
    int i = 5;
    printf("%d", ++i, i); /* Statement 1 */
}

只有一个%d。我很困惑,因为我在编译器GCC,Turbo C ++和Visual C ++中获得了6个输出。行为是定义得好还是什么?

这与我的last question相关。

5 个答案:

答案 0 :(得分:7)

由于两个原因,它未定义:

  1. i的值是两次使用而没有插入序列点(参数列表中的逗号不是逗号运算符,也不引入序列点)。

  2. 你在调用范围内没有原型的可变函数。

  3. 传递给printf()的参数数量与格式字符串不兼容。

  4. 默认输出流通常是行缓冲的。如果没有'\n',则无法保证输出有效输出。

答案 1 :(得分:6)

调用函数时会调用所有参数,即使它们未被使用,因此,由于函数参数的求值顺序未定义,因此再次使用UB。

答案 2 :(得分:3)

我认为它定义明确。 printf将第一个%占位符与第一个参数匹配,在这个实例中,第一个参数是一个预先增加的变量。

答案 3 :(得分:0)

根据this documentation,传递给格式字符串的任何其他参数都应被忽略。它还将mentions for fprintf对参数进行评估然后忽略。我不确定printf是否属于这种情况。

答案 4 :(得分:0)

评估所有参数。订单未定义。 C / C ++的所有实现(我都知道)从从右到左评估函数参数。因此i通常在++i之前进行评估。

在printf中,%d映射到第一个参数。其余的都被忽略了。

所以印刷6是正确的行为。

我认为从右到左的评估顺序已经非常陈旧(从第一个C编译器开始)。当然,在C ++发明之前,大多数C ++实现都会保持相同的评估顺序,因为早期的C ++实现只是转换为C.

从右到左评估函数参数有一些技术原因。在堆栈体系结构中,参数通常被压入堆栈。在C中,您可以调用一个参数多于实际指定的函数 - 额外的参数被简单地忽略。如果从左到右评估参数,并从左向右推,那么堆栈指针正下方的堆栈槽将保存最后一个参数,并且函数无法获得任何特定参数的偏移量(因为推送的实际参数数量取决于调用者)。

在从右到左的推送顺序中,堆栈指针正下方的堆栈插槽将始终保存第一个参数,下一个插槽保存第二个参数等。参数偏移将始终是函数的确定性(可能在其他地方编写和编译到一个库中,与它所在的地方分开。

现在,从右向左推送顺序并不强制从右到左评估顺序,但在早期的编译器中,内存很少。在从右到左的评估顺序中,相同的堆栈可以就地使用(实际上,在评估参数之后 - 可能是表达式或函数调用) ! - 返回值已经在堆栈的正确位置)。在从左到右的评估中,参数值必须单独存储,并以相反的顺序推回堆栈。

有兴趣了解从右到左评估背后的真实历史。