在C中是否有任何保证代码在未定义的行为之前?

在下面的代码中保证打印“0 \ n”?

#include  int main(void) { int c = 0; printf("%d\n",c); printf("%d,%d\n",++c,++c); } 

更一般地说,如果一个程序有未定义的行为,整个程序是不确定的,还是仅从开始出现问题代码的序列点开始?

请注意:我不是在询问编译器对第二个printf的作用。 我问的是第一个printf是否有保证会发生。

我知道未定义的行为能够炸毁你的计算机,崩溃你的程序或其他什么。

甚至忽略了“任何事情都可能发生!程序可以及时返回并防止自己在第一时间运行!”这样的事情,编译器很可能检测到某些forms的未定义行为而不能在这种情况下编译如果你不能让它在第一时间运行。 所以,是的,未定义的行为在原则上具有传染性,但在实践中大部分时间都不一定如此。

无论程序在导致未定义的行为之前做了什么,当然已经完成了。

因此printf()会将“0 \ n”发送到stdout流。 该数据是否真正进入设备取决于该流是否为无缓冲,缓冲或行缓冲。

然后,我认为在完成的,明确定义的操作之后执行的未定义行为可能会导致损坏程度,因为看起来明确定义的行为未正确完成。 我想有点像“如果一棵树落在树林里……”的东西。


更新以解决未来未定义行为意味着即使在程序开始执行之前所有赌注都已关闭的信念……

以下是C99标准关于在序列点之间多次修改对象值的说法:

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。

标准也有关于访问对象的说法:

访问

   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  #include  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)); /* possible undefined behavior */ return 0; } 

在这个程序中,甚至在转换时都不知道存在未定义的行为 – 只有当程序的输入指示应该处理相同的数组元素时才会出现(或者如果输入指定了可能发生不同类型的未定义行为,则会发生这种情况)无效的索引值)。

因此,让我们用这个程序提出同样的问题:标准对第一个printf()结果或副作用可能发生的事情有什么看法?

如果输入提供有效的索引值,则未定义的行为只能第一个printf() 之后发生。 假设输入是argv[1] == "1"argv[2] == "1" :编译器实现没有在第一个printf()之前确定的自由,因为未定义的行为将在某个时刻发生程序允许跳过第一个printf()并直接进入格式化硬盘的未定义行为(或任何其他可能发生的恐怖行为)。

鉴于编译器同意转换程序,未来未定义行为的承诺并不能让编译器在实际发生未定义行为之前自由地执行任何操作。 当然,正如我之前提到的,未定义行为造成的损害可能会破坏以前的结果 – 但这些结果必定已经发生。

未定义的行为取决于编译器供应商/随机机会。 这意味着它可能会抛出exception,程序中的数据损坏,写入你的mp3集合,调用天使,或点燃你的祖母着火。 一旦您有未定义的行为,您的整个程序将变得不确定。

一些编译器和一些编译器配置将提供一些方法,但是一旦打开优化,大多数程序的表现都会非常糟糕。

如果一个程序有未定义的行为,整个程序是不确定的,还是仅从开始出现问题代码的序列点开始?

运行到未定义点的代码可能已经做了正确的事情。 但这只会带来很多好处。 一旦你遇到未定义的行为, 任何事情都可能发生。 Murphy定律涵盖了是否发生某些事情:)

优化依赖于明确定义的行为,并在该框之外播放各种技巧以获得速度。 这意味着您的代码可以完全不按顺序执行,只要副作用对于定义良好的程序是无法区分的。 仅仅因为未定义的行为似乎从源代码中的某个点开始并不能保证以前的任何代码行都不会受到影响。 启用优化后,您的代码可以更加轻松地更早地触及未定义的行为。

深思熟虑:由各种类型的恶意软件实现的缓冲区溢出攻击严重依赖于未定义的行为。

对于未定义的行为,可能应该区分在编译时可检测到的事物(如您的情况)和与数据相关且仅在运行时发生的事物,例如意外写入const限定对象。

对于后来程序必须运行直到UB发生的地方,因为它通常不能事先检测到它(模型检查对于非平凡的程序来说是一项艰巨的任务),对于你的情况,它可能被允许产生任何类型的程序,例如发送一些钱到编译器供应商左右;-)

更合理的选择是不产生任何东西,即抛出错误而根本不编译。 一些编译器在被告知时会这样做,例如使用gcc你可以使用-Wall -std=c99 --pedantic -Werror