在C++11 is it Undefined Behavior中,但在C中是while(1);
是未定义的行为吗?
答案 0 :(得分:22)
这是明确定义的行为。在C11中增加了新的第6.8.5条第6款
一个迭代语句,其控制表达式不是一个不执行输入/输出操作的常量表达式, 156),不访问volatile对象,并且在其体内不执行同步或原子操作,控制表达式,或(在for语句的情况下)其表达式-3,可以由实现假定终止。 157)
157) 这是为了允许编译器转换,例如即使无法证明终止也可以删除空循环。
由于循环的控制表达式是常量,编译器可能不会认为循环终止。这适用于应该永远运行的被动程序,如操作系统。
但是对于以下循环,行为不清楚
a = 1; while(a);
实际上,编译器可能会也可能不会删除此循环,从而导致程序可能会终止或无法终止。这不是真正的未定义,因为它不允许擦除你的硬盘,但它是一个避免的结构。
然而,还有另一个障碍,请考虑以下代码:
a = 1; while(a) while(1);
既然编译器可能假设外部循环终止,那么内部循环也应该终止,外部循环如何终止。因此,如果你有一个非常聪明的编译器,那么不应该终止的while(1);
循环必须在main
之前一直有这样的非终止循环。如果你真的想要无限循环,你最好在其中读或写一些volatile
变量。
为什么这个条款不实用
我们的编译器公司不太可能会使用这个子句,主要是因为它是一个非常语法的属性。在中间表示(IR)中,上述例子中常数和变量之间的差异很容易通过不断传播而丢失。
该子句的目的是允许编译器编写者应用所需的转换,如下所示。考虑一个不那么罕见的循环:
int f(unsigned int n, int *a)
{ unsigned int i;
int s;
s = 0;
for (i = 10U; i <= n; i++)
{
s += a[i];
}
return s;
}
出于架构原因(例如硬件循环),我们希望将此代码转换为:
int f(unsigned int n, int *a)
{ unsigned int i;
int s;
s = 0;
for (i = 0; i < n-9; i++)
{
s += a[i+10];
}
return s;
}
如果没有第6.8.5条第6款,这是不可能的,因为如果n
等于UINT_MAX
,则循环可能不会终止。然而,对于一个人来说,很明显这不是本代码作者的意图。条款6.8.5和6现在允许这种转变。然而,对于编译器编写器来说,实现这种方式并不是很实用,因为无限循环的语法要求很难在IR上维护。
请注意,n
和i
必须unsigned
,因为signed int
上的溢出会给出未定义的行为,因此可以证明转换是正当的。然而,除了较大的正范围之外,使用unsigned
也可以获得高效的代码。
另一种方法
我们的方法是代码编写者必须通过例如在循环之前插入assert(n < UINT_MAX)
或者像Frama-C之类的保证来表达他的意图。这样编译器就可以“证明”终止,而不必依赖第6.8.5条第6款。
答案 1 :(得分:5)
在检查the draft C99 standard后,我会说“不”,这不是未定义的。我在草案中找不到任何语言提到迭代结束的要求。
描述迭代语句语义的段落全文如下:
迭代语句会导致一个名为循环体的语句 重复执行,直到控制表达式比较等于0。
如果适用,我希望有任何限制,例如为C ++ 11指定的限制。还有一个名为“约束”的部分,它也没有提到任何这样的约束。
当然,实际的标准可能会说别的,但我对此表示怀疑。
答案 2 :(得分:1)
C11 6.8.5 Iteration statements /6
中显示以下语句:
其控制表达式不是常量表达式且不执行输入/输出操作的迭代语句不访问volatile 对象,并且在其主体中不执行同步或原子操作,控制表达式,或者(在for语句的情况下)其表达式-3,可以由实现假定终止。
由于while(1);
使用一个常量表达式,因此不允许实现假设它将终止。
编译器 可以完全删除这样的循环,表达式是非常量的,并且所有其他条件都被类似地满足,即使无法确定循环终止也是如此。
答案 3 :(得分:1)
最简单的答案涉及§5.1.2.3p6的引用,其中陈述了符合实现的最低要求:
符合实施的最低要求是:
- 对volatile对象的访问严格按照 抽象机器的规则。
- 程序终止时,所有写入文件的数据都应该是 与根据该程序执行程序的结果相同 抽象语义会产生。
- 交互设备的输入和输出动态应采用 按7.21.3的规定放置。这些要求的目的是 无缓冲或行缓冲输出尽快出现 确保在程序之前实际显示提示消息 等待输入。
这是该计划的可观察行为。
如果机器代码由于执行优化而无法产生可观察行为,则编译器不是C编译器。在终止点上只包含这种无限循环的程序的可观察行为是什么?这种循环可能结束的唯一方法是通过一个使其过早结束的信号。在SIGTERM
的情况下,程序终止。这不会导致任何可观察到的行为。因此,该程序唯一有效的优化是编译器抢占系统关闭程序并生成一个立即结束的程序。
/* unoptimised version */
int main() {
for (;;);
puts("The loop has ended");
}
/* optimised version */
int main() { }
一种可能性是引发信号并调用longjmp以使执行跳转到不同的位置。似乎唯一可以跳转到的地方是在循环之前执行期间到达的地方,因此提供编译器足够智能以注意到信号被引发导致执行跳转到其他地方,它可能潜在地优化循环(和信号升高)偏离立即跳跃。
当多个线程进入等式时,有效的实现可能能够将程序的所有权从主线程转移到另一个线程,并结束主线程。无论优化如何,程序的可观察行为仍必须是可观察的。