在程序引起未定义行为之前所做的任何事情,当然已经完成了。
因此,
printf()
已将 "0\n" 发送到
stdout
流。数据是否实际到达设备取决于该流是无缓冲、有缓冲还是行缓冲。
另一方面,我想未定义行为在完成的明确定义操作之后执行可能会造成损坏,以至于似乎明确定义的行为没有正确完成。我猜就像那些“如果树倒在树林里……”的事情一样。
更新以解决未来未定义行为意味着所有赌注都失效甚至在程序开始执行之前的信念……
以下是 C99 标准对在序列点之间多次修改对象值的情况的说明:
在上一个序列点和下一个序列点之间,通过表达式计算最多只能修改对象的存储值一次。
标准还对访问对象进行了如下说明:
access
<execution-time action> to read or modify the value of an object
NOTE 1 Where only one of these two actions is meant, ``read'' or ``modify'' is used.
NOTE 2 "Modify'' includes the case where the new value being stored is the same as the previous value.
NOTE 3 Expressions that are not evaluated do not access objects.
我认为在两个序列点之间多次修改对象不是翻译时的"未定义行为",因为对象不会在翻译时被访问或修改。
即便如此,我同意编译器在编译时能够诊断出这种未定义行为是一件好事,但我还是认为只有成功编译的程序才适用于这个问题更有趣。所以我们稍微改变一下问题,提供一个情况,使得编译器无法在翻译时诊断出未定义行为:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int c[] = { 0, 1, 2, 3 };
int *p1 = &c[0];
int *p2 = &c[1];
if (argc > 1) {
p1 = &c[atoi(argv[1])];
}
if (argc > 2) {
p2 = &c[atoi(argv[2])];
}
printf("before: %d, %d\n", *p1, *p2);
printf("after: %d, %d\n", ++(*p1),++(*p2));
return 0;
}
在这个程序中,未定义的行为甚至在编译时都无法知道 - 只有当程序的输入指示应处理相同的数组元素(或者如果输入指定无效的索引值,则可能发生不同类型的未定义行为)时才会发生。
因此,让我们用这个程序提出同样的问题:标准对第一个
printf()
结果或副作用可能发生的情况有何规定?
如果输入提供有效的索引值,则未定义的行为只能在第一个
printf()
之后发生。假设输入是
argv[1] == "1"
和
argv[2] == "1"
:编译器实现不能在第一个
printf()
之前确定,因为未定义的行为将在程序的某个时刻发生,所以允许跳过第一个
printf()
并直接进入其格式化硬盘(或任何其他可能发生的恐怖事件)的未定义行为。
考虑到编译器同意翻译程序,未来的未定义行为承诺并不给编译器在实际发生未定义行为之前就可以做任何想做的事情的自由。当然,正如我之前提到的,未定义行为造成的损害可能会破坏先前的结果 - 但这些结果必须已经发生过。