是printf的输出(“%d%d”,c ++,c); 还未定义?
我最近碰到一篇文章cout << c ++ << c ;?的正确答案是什么? 并想知道是否输出
int c = 0; printf ("%d %d", c++, c);
也是未定义的?
我在讲座中学过后缀和前缀运算符只有在得到分号后才增加数值。 所以根据我,输出0 0
是正确的!
我在讲座中学过后缀和前缀运算符只有在得到分号后才增加数值。
把你的讲师寄给我,让我可以礼貌地指出他的错误。
确切地说,应用前或后缀++
和--
的副作用是未指定的 ,除了要求在下一个序列点之前发生。 在像expression式
x = a++ * b
a
可以在a++
被评估之后立即更新,或者可以推迟更新,直到a++ * b
已经被评估并且结果被分配给x
或其间的任何地方。
这就是为什么expression式如i++ * i++
和printf("%d %d", c++, c)
和a[i++] = i
以及其他一些expression式都是不好的。 你会得到不同的结果,基于编译器,优化设置,周围的代码等等。语言标准明确地将行为定义为未定义的,以便编译器没有义务“做正确的事情”,无论正确的事情是什么。 请记住, 未定义行为的定义是
3.4.3
1 未定义的行为
行为,在使用不可移植或错误的程序结构或错误的数据时,本国际标准对此没有要求2注意可能存在未定义的行为范围,包括忽略完全不可预知的结果,在翻译或程序执行期间以文档化的方式performance环境特征(无论是否发布诊断消息),终止翻译或执行发出诊断消息)。
3示例未定义行为的示例是整数溢出行为。
这是一个深思熟虑的devise决定 – 未明确说明这些操作的顺序的基本原理是给予执行自由来重新排列评估顺序以达到优化目的。 但是,为了换取这种自由,某些行动将不会有明确的结果。
请注意,编译器可以自由地尝试检测这些情况并发出诊断; printf("%d %d", c++, c);
很容易就可以捕捉到,但是在一般情况下,这将是一个侦测器。 想象一下,如果这已经被写成printf("%d %d", (*p)++, c)
; 如果p
指向c
,那么行为是不确定的,否则没关系。 如果将p
分配给不同的翻译单元,那么在编译时无法知道这是否是个问题。
这个概念并不难理解,但却是C语言中一直被误解(和错误教授 )的方面之一。 毫无疑问,这就是为什么Java和C#语言规范强制执行所有操作的特定评估顺序(所有操作数从左到右评估,所有副作用都立即应用)。
我在讲座中学过后缀和前缀运算符只有在得到分号后才增加数值
这不是标准描述的方式。 一个序列点是代码中的一个点,代码的前面部分可能出现的副作用已被评估。 参数之间的逗号不是一个顺序点,所以这里的行为是不确定的。
函数参数的评估顺序是未指定的。 不能保证函数的参数将按照(1, 2, N)
的顺序进行计算,所以不能保证在第二个parameter passing之前计算增量。
所以根据我,输出0 0是正确的!
不,行为是不确定的,所以你不能合理地声称输出将是0 0。
程序的行为是不确定的,因为它违反了6.5expression式的要求:
在前一个序列点和下一个序列点之间,一个对象应该通过评估一个expression式来最多修改其存储值。 此外,先前的值应该是只读的,以确定要存储的值。
c++
和c
都是在没有插入序列点的情况下被评估的,并且读取c
的先前值以确定由c++
存储的值,并且确定expression式c
的值。
由于参数未定义的评估顺序,行为肯定是未定义的。 你可以certificate这个“未定义的输出”做一些随机testing:
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
你是对的:它是未定义的。 原因在于,虽然可以保证printf()
的三个参数在调用printf()
之前被计算,但是三个参数的计算顺序是不确定的。
顺便说一下,在分号之后出现增量在技术上是不正确的。 标准的保证是增量不会晚于分号。 [其实在你的情况下,我认为这个标准保证了它在控制传递到printf()
函数之前就已经发生了 – 但是现在这个答案已经开始分解成琐碎的琐事了,所以让我来解释一下在那里rest!]
无论如何,总之,你是对的。 行为是不确定的。
更新:作为@ R ..正确地观察,未定义的行为来自参数之间缺乏序列点。 这个标准对于不明确和不明确的分词非常小心,所以接受了更正,谢谢。
这个程序展示了未指定行为和未定义行为的组合。 从不明确的行为开始,第6.5
节第3
段中的C99标准草案说:
除了后面的规定(对于函数调用(),&&,||,?:和逗号运算符), 子expression式的评估顺序和哪些副作用发生都没有说明。
它还说,除了后面的规定,特别引用function-call ()
,所以我们在后面的6.5.2.2
函数调用第10
段中的草案标准中看到:
实际参数中的函数指示符,实际参数和子expression式的评估顺序没有指定 ,但实际调用之前有一个顺序点。
所以我们不知道C或C ++的评估是否会先在这行代码中出现:
printf ("%d %d", c++, c);
此外,在第6.5.2.4
节中, 后缀递增和递减操作符第2
段说:
[…] 获得结果后,操作数的值递增。 […] 更新操作数存储值的副作用应发生在上一个和下一个序列点之间。
所以我们知道的是,当执行后增量c
将被更新之后,它的值被读取,但之前的下一个序列点是正确的printf
被调用,但没有别的。 至于未定义的行为 ,如果我们看标准草案第6.5
节第2
段,就是说:
在前一个序列点和下一个序列点之间,一个对象应该通过评估一个expression式来最多修改其存储值。 此外,先前的值应该是只读的,以确定要存储的值。
在printf
expression式中,先前的值正在被读取,以便同时评估C ++和C ,所以我们现在处于未定义的领域。