20

我最近看到一个帖子cout << c++ << c; 的正确答案是什么?并且想知道是否输出

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

也是未定义的??

我在讲座中研究过,后缀和前缀运算符仅在获得分号后才会增加值。所以在我看来,输出0 0是正确的!!!

4

6 回答 6

19

我在讲座中研究过,后缀和前缀运算符仅在获得分号后才会增加值。

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

确切地说,当应用前缀或后缀++的副作用时未指定,除了要求它发生在下一个序列点之前。在像这样的表达中--

x = a++ * b

a可以在a++评估后立即更新,或者可以推迟更新,直到a++ * b评估完毕并将结果分配给x,或介于两者之间的任何位置。

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

3.4.3

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

2 注:可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间的行为环境的记录方式特征(有或没有诊断消息的发布),以终止翻译或执行(有诊断消息的发布)。

3 示例 未定义行为的一个示例是整数溢出行为。

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

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

这个概念并不难理解,但它是 C 语言中最常被误解(和误传的方面之一。毫无疑问,这就是为什么 Java 和 C# 语言规范对所有内容都强制使用特定的求值顺序(所有操作数从左到右求值,并且所有副作用都会立即应用)。

于 2012-06-01T14:20:26.417 回答
6

我在讲座中研究过后缀和前缀运算符仅在获得分号后才会增加值

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

未指定函数参数的评估顺序。不能保证函数的参数将按顺序计算(1, 2, N),因此不能保证在传递第二个参数之前计算增量。

所以在我看来,输出 0 0 是正确的!!!

不,行为未定义,因此您不能合理地声称输出将为 0 0。

于 2012-06-01T06:02:18.793 回答
3

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

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

c++并且c都在没有中间序列点的情况下进行评估,并且c读取 的先验值以确定要由 存储的值c++,并确定表达式 的值c

于 2012-06-01T06:11:05.450 回答
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
于 2012-06-01T06:08:28.123 回答
1

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

顺便说一句,仅在分号之后发生递增在技术上是不正确的。标准保证增量不会晚于分号。[实际上,在你的情况下,我相信标准保证它会在控制权传递给printf()函数之前发生——但现在这个答案开始演变成迂腐琐事的领域,所以让我把事情放在那里吧! ]

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

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

于 2012-06-01T06:02:08.107 回答
1

该程序展示了未指定行为未定义行为的组合。从未指定的行为开始,部分段落中的C99 标准草案说:6.53

运算符和操作数的分组由语法指示。74) 除稍后指定(对于函数调用 ()、&&、||、?: 和逗号运算符)外,子表达式的求值顺序和中的顺序发生哪些副作用都未指定。

它还说 except as specified as later and specific cites function-call (),所以我们看到稍后在6.5.2.2 函数调用段落中的标准草案中10说:

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

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

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

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

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

所以我们所知道的是,在执行后增量时,c将在读取其值之后但在调用之前的下一个序列点之前更新,printf但没有别的。至于未定义的行为,如果我们查看标准草案中的部分6.5段落2,则说:

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

printf表达式c的先验值被读取以评估C++C,因此我们现在处于未定义的领域。

于 2013-08-16T19:11:31.753 回答