C++ 在C/C++;(第二部分)

C++ 在C/C++;(第二部分),c++,c,undefined-behavior,C++,C,Undefined Behavior,关于序列点的规则对以下代码说明了什么 int main(void) { int i = 5; printf("%d", ++i, i); /* Statement 1 */ } 只有一个%d。我很困惑,因为我得到6作为编译器在GCC,Turbo C++和Visual C++中的输出。行为是否有明确的定义 这与我的工作有关。我认为它定义得很好。printf将第一个%占位符与第一个参数相匹配,在本例中,第一个参数是一个预递增的变量。调用函数时会对所有参数进行求值,即使它们未被使用,

关于序列点的规则对以下代码说明了什么

int main(void) {
    int i = 5;
    printf("%d", ++i, i); /* Statement 1 */
}
只有一个
%d
。我很困惑,因为我得到6作为编译器在GCC,Turbo C++和Visual C++中的输出。行为是否有明确的定义


这与我的工作有关。

我认为它定义得很好。printf将第一个%占位符与第一个参数相匹配,在本例中,第一个参数是一个预递增的变量。

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

根据,应忽略传递给格式字符串的任何附加参数。它还表示将对参数求值,然后忽略该参数。我不确定printf是否是这种情况。

由于两个原因,它没有定义:

  • i
    的值在没有中间序列点的情况下使用了两次(参数列表中的逗号不是逗号运算符,也不引入序列点)

  • 您正在调用一个范围内没有原型的变量函数

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

  • 默认输出流通常是行缓冲的。如果没有
    “\n”
    ,则无法保证输出将有效输出


  • 对所有参数进行求值。订单未定义。所有C/C++(据我所知)的实现都从右向左计算函数参数。因此,
    i
    通常在
    ++i
    之前进行评估

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

    所以打印6是正确的行为

    我相信从右到左的求值顺序已经非常古老了(从第一个C编译器开始)。当然,在C++被发明之前,大多数C++的实现都会保持相同的评估顺序,因为早期的C++实现简单地转化为C.< 从右向左计算函数参数有一些技术原因。在堆栈体系结构中,参数通常被推送到堆栈上。在C语言中,您可以使用比实际指定的参数更多的参数调用函数——额外的参数将被类似地忽略。如果参数从左向右求值,并从左向右推送,则堆栈指针下的堆栈插槽将保存最后一个参数,并且函数无法获取任何特定参数的偏移量(因为推送的参数的实际数量取决于调用方)

    在从右到左的推送顺序中,堆栈指针右下方的堆栈插槽将始终保存第一个参数,下一个插槽保存第二个参数等。参数偏移量对于函数而言始终是确定的(可以在其他位置写入并编译到库中,与调用它的位置分开)

    现在,从右到左的推送顺序并不强制要求从右到左的求值顺序,但在早期的编译器中,内存是稀缺的。按照从右到左的求值顺序,可以在适当的位置使用相同的堆栈(基本上,在求值参数之后——可能是表达式或函数调用!——返回值已经位于堆栈的正确位置)。在从左到右的计算中,参数值必须单独存储,参数值必须按相反顺序推回堆栈


    我很想知道从右到左的评估背后的真实历史。

    Cripes我希望这种代码不要太过本地化。投票通过关闭。仅仅因为代码调用了未定义的行为并不意味着你不能得到一致的结果。@jaya:好的,我必须问一下。你为什么接受了一个不正确的答案?@Space\u c0w0y:接受的答案绝不是不正确的。它是未定义的,因为这些参数仍然像正常情况一样进行计算;唯一的区别是printf不会使用该值。@DeadMG这不意味着它已经定义了吗?由于第二个参数不修改值,printf也不使用第二个参数,因此它的值与输出/行为无关。@forsvarir:Say
    i=5
    。如果先计算
    i
    ,则调用函数时使用
    6,5
    作为参数。如果先计算
    ++i
    ,则调用函数时使用
    6,6
    作为参数。这在这里没有区别,但在其他情况下可能会有区别。@Space\u C0wb0y:我想这取决于您如何定义“定义的行为”。我同意传递给printf的内容没有定义,因此需要谨慎,但是对于给定的示例,定义了可测量的行为(它将始终输出6到stdout)。但我愿意承认我可能误解了这个问题:)行为是不确定的。它与传递给printf的内容无关,也与是否使用printf无关。该标准规定,如果修改对象,并且在没有插入序列点的情况下在其他位置访问该对象,则行为是未定义的,其他序列点用于确定新值。至少在理论上,printf可能永远不会被调用;程序在此之前可能会崩溃。
    3
    不是正确的原因。参数的数量可以大于格式说明符的数量。顺便说一句,感谢@Prasoon,你说得对,我也不知道4是什么。标准输出在程序退出时刷新。终端对非行终止的输出所做的操作超出了标准的范围,但无论标准输出是否缓冲,都是从程序中写入的。谢谢@Steve。我倾向于考虑缺少终止<代码> \n′/Cord>一个错误,因为我使用的一些工具在最后一行断线后不考虑字符。这是实用程序中的一个bug,而不是C语言中的:),或者可能是这个程序中的一个bug,因为它没有编写那些实用程序所期望的输出格式