【问题标题】:Is this undefined behavior in C/C++ (Part 2) [duplicate]这是 C/C++ 中未定义的行为吗(第 2 部分)[重复]
【发布时间】:2011-07-20 09:40:05
【问题描述】:

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

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

只有一个%d。我很困惑,因为我在编译器 GCC、Turbo C++ 和 Visual C++ 中得到 6 作为输出。行为是否明确定义?

这和我的last question有关。

【问题讨论】:

  • Cripes 我希望这种代码不会在野外生存..
  • 过于本地化。投票结束。
  • 仅仅因为代码调用未定义的行为并不意味着您不会得到一致的结果。
  • @jaya:好的,我得问一下。为什么你接受了一个不正确的答案?
  • @Space_C0wb0y :接受的答案绝不是错误的。

标签: c++ c undefined-behavior


【解决方案1】:

根据this documentation,传递给格式字符串的任何附加参数都应被忽略。 mentions for fprintf 也将评估该参数,然后将其忽略。我不确定 printf 是否是这种情况。

【讨论】:

  • 每个参数都在函数调用/之前/进行评估,printf /then/ 决定忽略其中一些。尽管如此,您还是提供了这些参数,因此您有未定义的行为™。
【解决方案2】:

未定义有两个原因:

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

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

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

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

【讨论】:

  • 3 不是正确的原因。您的参数数量可以大于格式说明符的数量。 BTW +1 提到 2。
  • 谢谢@Prasoon,你是对的
  • 我也不确定 4。 stdout 在程序退出时被刷新。终端对非行终止的输出所做的操作超出了标准的范围,但无论标准输出如何缓冲或不缓冲,它都被写出程序。
  • 谢谢@Steve。我倾向于认为缺少终止 '\n' 是一个错误,因为我使用的一些实用程序不考虑最后一个换行符之后的字符。这是实用程序中的错误,而不是 C 语言中的错误 :)
  • 或者可能是这个程序中的一个错误,因为它没有编写这些实用程序所期望的输出格式。只是不是 UB 错误。
【解决方案3】:

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

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

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

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

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

在从右到左的推送顺序中,堆栈指针正下方的堆栈槽将始终保存第一个参数,下一个槽保存第二个参数,依此类推。参数偏移量对于函数始终是确定性的(可能在其他地方编写并编译到库中,与调用它的地方分开)。

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

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

【讨论】:

    【解决方案4】:

    所有参数在调用函数时都会被计算,即使它们没有被使用,所以,由于函数参数的计算顺序是未定义的,你又得到了 UB。

    【讨论】:

    • 从技术上讲,它不是 UB,而是未指定的行为。请参见 ISO 9899:1999 附录 J.1 第一页的最后一段。
    • @Lundin:我不确定在 C++03 中是否与在 C99 中相同。
    • @Lundin:在 C++ 中,函数参数的求值顺序是未指定的,这意味着在这种情况下,由于 5/4(最后一句),行为是未定义的。 C 不是很明确,如果 any 允许的顺序违反了规则,那么行为是未定义的,但在这种情况下,评估参数的一种顺序违反了 6.5/2 的“shall”规则,并且其他没有。由于您无法预测它们将被评估的顺序,因此未指定行为是否已定义,这不是一个好地方。
    • @Steve Jessop:我认为这是一个有趣的讨论点,但我不太确定。执行顺序未指定,但只有两种可能的结果,i++i 在另一个之前执行。因为i 没有副作用并且被函数忽略("%d" 将只使用第一个参数),所以printf 的输出必然是固定的。我知道我在这里踩了一条细线,我不太确定这一点,但我不认为它是未定义的。
    • @David Rodríguez - dribeas :经过一番研究,我发现了一个确切的骗局:stackoverflow.com/questions/3450582/…
    【解决方案5】:

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

    【讨论】:

    • 它是未定义的,因为这些参数仍然像正常一样被评估;唯一的区别是 printf 不会使用那个值。
    • @DeadMG 不是说它已经定义了吗?由于第二个参数不修改值并且 printf 不使用第二个参数,因此它的值与输出/行为无关。
    • @forsvarir:说i=5。如果首先评估i,则使用6,5 作为参数调用该函数。如果首先评估++i,则以6,6 作为参数调用该函数。它在这里没有区别,但在其他情况下可能会。
    • @Space_C0wb0y:我想这取决于你如何定义“定义的行为”。我同意没有定义传递给 printf 的内容,因此需要谨慎,但是对于给出的示例,定义了可测量的行为(它总是将 6 输出到标准输出)。但我愿意承认我可能误解了这个问题:)
    • 行为未定义。它与传递给 printf 的内容或是否使用无关。该标准表示,如果您修改一个对象,并且您在没有中间序列点的情况下在其他地方访问它,那么行为是未定义的,除了确定新值之外。至少从理论上讲, printf 可能永远不会被调用;在此之前程序可能会崩溃。
    猜你喜欢
    • 1970-01-01
    • 2018-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-14
    相关资源
    最近更新 更多