是printf的输出(“%d%d”,c ++,c);也未定义?

时间:2012-06-01 05:56:24

标签: c undefined-behavior

我最近遇到了帖子What is the correct answer for cout << c++ << c;?,并想知道是否输出

int c = 0;  
printf ("%d %d", c++, c);  

也未定义?

我在讲座中研究了修复后和前缀运算符只有在得到分号后才会增加值。所以根据我的说法,输出0 0是正确的!!!

6 个答案:

答案 0 :(得分:19)

  

我已经在讲座中研究过修复后和前缀运算符只有在得到分号后才会增加值。

把你的讲师送给我,这样我就可以<打击>给他拿棒球棒礼貌地指出他的错误。

正确应用前缀++--的副作用是未指定,除了要求它在下一个序列点之前发生。在像

这样的表达式中
x = a++ * b

a可能会在评估a++后立即更新,或者可能会推迟更新,直到评估a++ * b并将结果分配到x或任何地方之间。

这就是为什么像i++ * i++printf("%d %d", c++, c)以及a[i++] = i这样的表达式以及其他许多表达都不好的原因。您将根据编译器,优化设置,周围代码等获得不同的结果。语言标准明确地保留行为 undefined ,以便编译器没有义务“做正确的事”,无论如何正确的事情可能是。请记住,未定义行为的定义是

3.4.3

1 未定义的行为
在使用不可移植或错误的程序结构或错误数据时, 本国际标准没有要求的任何要求

2注意可能的未定义行为范围包括完全忽略不可预测的情况 结果,在翻译或程序执行过程中以文件化的方式表现 环境(有或没有发出诊断信息),终止翻译或 执行(发出诊断信息)。

3示例未定义行为的示例是整数流上的行为。

这是一个深思熟虑的设计决策 - 未指定这些操作的顺序的基本原理是为了实现自由,重新安排评估顺序以进行优化。但是,为了换取这种自由,某些操作不会有明确的结果。

请注意,编译器可以尝试自由检测这些情况并发出诊断信息; printf("%d %d", c++, c);很容易捕获,但在一般情况下这将是一个错误的检测器。想象一下,如果写的是printf("%d %d", (*p)++, c);如果p指向c,则行为未定义,否则可以。如果在不同的翻译单元中分配p,那么在编译时无法知道这是否是一个问题。

这个概念并不难理解,但却是C语言中最常被误解(以及错误教授)的方面之一。毫无疑问,这就是Java和C#语言规范强制对所有内容执行特定评估顺序的原因(所有操作数从左到右进行评估,并立即应用所有副作用)。

答案 1 :(得分:6)

  

我已经在讲座中研究过修复后和前缀运算符只有在得到分号后才增加值

这不是标准描述的方式。 sequence point是代码中的一个点,其中已经评估了代码的先前部分中可能发生的副作用。函数参数之间的逗号不是序列点,因此其行为未定义。

未指定函数参数的评估顺序。无法保证函数的参数将按(1, 2, N)的顺序进行求值,因此无法保证在第二个参数传递之前将对增量进行求值。

  

所以根据我的说法,输出0 0是正确的!!!

不,行为未定义,因此您无法合理声明输出为0 0.

答案 2 :(得分:3)

程序的行为未定义,因为它违反了6.5表达式的要求:

  

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。此外,先前的值应该是只读的,以确定要存储的值。

c++c都是在没有插入序列点的情况下进行评估的,并且同时读取c的先前值以确定c++要存储的值,并确定表达式c的值。

答案 3 :(得分:2)

由于参数的未定义评估顺序,行为肯定是未定义的。你可以证明这个“未定义的输出”做了一些随机测试:

printf("%d %d\n", c++, c);
// result: 0 1
printf("%d %d %d\n", c++, c, c++);
// result: 1 2 0
printf("%d %d %d %d\n", c++, c++, c++, c);
// result: 2 1 0 3
printf("%d %d %d %d\n", c++, c, c++, c);
// result: 1 2 0 2
printf("%d %d %d %d\n", c++, c, c, c);
// result: 0 1 1 1

答案 4 :(得分:1)

你是对的:它是未定义的。原因是,虽然可以保证在调用printf()之前评估printf()的三个参数,但是未定义评估三个参数的顺序。

技术上不正确的是,仅在分号之后才进行增量。标准保证的是增量不会晚于分号。 [实际上,在你的情况下,我相信标准保证它会在控制传递给printf()函数之前发生 - 但是现在这个答案开始转向迂腐琐事的领域,所以让我让此事在那里休息!]

无论如何,总之,你是对的。行为未定义。

更新:正如@R ..正确地观察到的,未定义的行为来自于参数之间缺少序列点。关于分词未指定 undefined,,标准非常谨慎,因此感谢接受更正。

答案 5 :(得分:1)

此计划同时展示了unspecified behaviorundefined behavior的组合。从未指定的行为开始,6.53部分中的draft C99 standard说:

  

运算符和操作数的分组由语法表示.74)   除非后面指定(对于函数调用(),&amp;&amp;,||,?:和   逗号运算符),子表达式的评估顺序和   发生副作用的顺序都是未指定的。

它除了后面指定并且特别引用function-call ()之外还说明了,因此我们稍后会在6.5.2.2 部分{em>函数调用10部分的标准草案中看到表示:

  

功能指示符的评估顺序,实际   参数和实际参数中的子表达式是   未指定,但在实际调用之前有一个序列点。

所以我们不知道 C 的读取或 C ++ 的评估是否会在这行代码中首先发生:

printf ("%d %d", c++, c); 

此外,在6.5.2.4 部分后缀增量和减量运算符2中说:

  

[...] 获得结果后,操作数的值递增。 [...] 更新操作数存储值的副作用应该发生在前一个和下一个序列点之间。

所以我们所知道的是,在执行帖子增量时c会在读取其值之后但在调用printf之前的下一个sequence point之前更新,但没有其他内容。至于未定义的行为,如果我们从标准草案中查看6.52部分,则说:

  

在上一个和下一个序列点之间,对象应具有其存储值   通过表达式的评估最多修改一次。 此外,先前的价值   应只读以确定要存储的值。

printf表达式中c的先前值正在读取,以便同时评估 C ++ C ,因此我们现在处于未定义领域。