简体   繁体   English

是while(1); C中的未定义行为?

[英]Is while(1); undefined behavior in C?

In C++11 is it Undefined Behavior , but is it the case in C that while(1); C ++ 11中,它是未定义的行为 ,但在C中是这样的, while(1); is Undefined Behavior? 是未定义的行为?

It is well defined behavior. 这是明确定义的行为。 In C11 a new clause 6.8.5 ad 6 has been added 在C11中增加了新的第6.8.5条款第6款

An iteration statement whose controlling expression is not a constant expression, 156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate. 一个迭代语句,其控制表达式不是常量表达式, 156)不执行输入/输出操作,不访问易失性对象,并且不在其体内执行同步或原子操作,控制表达式,或(在for的情况下)声明)其表达式-3,可以由实现假定终止。 157) 157)


157) This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven. 157) 这是为了允许编译器转换,例如即使在无法证明终止时也删除空循环。

Since the controlling expression of your loop is a constant, the compiler may not assume the loop terminates. 由于循环的控制表达式是常量,编译器可能不会认为循环终止。 This is intended for reactive programs that should run forever, like an operating system. 这适用于应该永远运行的被动程序,如操作系统。

However for the following loop the behavior is unclear 但是对于以下循环,行为不清楚

a = 1; while(a);

In effect a compiler may or may not remove this loop, resulting in a program that may terminate or may not terminate. 实际上,编译器可能会或可能不会删除此循环,从而导致程序可能终止或可能不终止。 That is not really undefined, as it is not allowed to erase your hard disk, but it is a construction to avoid. 这不是真正的未定义,因为它不允许擦除你的硬盘,但它是一个避免的结构。

There is however another snag, consider the following code: 然而,还有另一个障碍,请考虑以下代码:

a = 1; while(a) while(1);

Now since the compiler may assume the outer loop terminates, the inner loop should also terminate, how else could the outer loop terminate. 现在,由于编译器可能假设外部循环终止,内部循环也应该终止,外部循环如何终止。 So if you have a really smart compiler then a while(1); 所以,如果你有一个非常聪明的编译器,那么一段while(1); loop that should not terminate has to have such non-terminating loops around it all the way up to main . 不应该终止的循环必须有这样的非终止循环,一直到main If you really want the infinite loop, you'd better read or write some volatile variable in it. 如果你真的想要无限循环,你最好在其中读取或写入一些volatile变量。

Why this clause is not practical 为什么这个条款不实用

It is very unlikely our compiler company is ever going to make use of this clause, mainly because it is a very syntactical property. 我们的编译器公司不太可能会使用这个子句,主要是因为它是一个非常语法的属性。 In the intermediate representation (IR), the difference between the constant and the variable in the above examples is easily lost through constant propagation. 在中间表示(IR)中,通过恒定传播容易丢失上述示例中的常数和变量之间的差异。

The intention of the clause is to allow compiler writers to apply desirable transformations like the following. 该子句的目的是允许编译器编写者应用所需的转换,如下所示。 Consider a not so uncommon loop: 考虑一个不那么罕见的循环:

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;
}

For architectural reasons (for example hardware loops) we would like to transform this code to: 出于架构原因(例如硬件循环),我们希望将此代码转换为:

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;
}

Without clause 6.8.5 ad 6 this is not possible, because if n equals UINT_MAX , the loop may not terminate. 如果没有第6.8.5和第6条,这是不可能的,因为如果n等于UINT_MAX ,则循环可能不会终止。 Nevertheless it is pretty clear to a human that this is not the intention of the writer of this code. 然而,对于一个人来说,很明显这不是本代码作者的意图。 Clause 6.8.5 ad 6 now allows this transformation. 条款6.8.5和6现在允许这种转变。 However the way this is achieved is not very practical for a compiler writer as the syntactical requirement of an infinite loop is hard to maintain on the IR. 然而,对于编译器编写器来说,实现这种方式并不是很实用,因为无限循环的语法要求很难在IR上维护。

Note that it is essential that n and i are unsigned as overflow on signed int gives undefined behavior and thus the transformation can be justified for this reason. 请注意, ni必须是unsigned因为signed int上的溢出会给出未定义的行为,因此可以证明转换是正确的。 Efficient code however benefits from using unsigned , apart from the bigger positive range. 然而,除了更大的正范围之外,使用unsigned高效代码也会受益。

An alternative approach 另一种方法

Our approach would be that the code writer has to express his intention by for example inserting an assert(n < UINT_MAX) before the loop or some Frama-C like guarantee. 我们的方法是代码编写器必须通过例如在循环之前插入assert(n < UINT_MAX)或者像Frama-C一样保证来表达他的意图。 This way the compiler can "prove" termination and doesn't have to rely on clause 6.8.5 ad 6. 这样编译器就可以“证明”终止,而不必依赖第6.8.5条款6。

PS: I'm looking at a draft of April 12, 2011 as paxdiablo is clearly looking at a different version, maybe his version is newer. PS:我正在看2011年4月12日的选秀,因为paxdiablo显然正在寻找一个不同的版本,也许他的版本更新。 In his quote the element of constant expression is not mentioned. 在他的引文中,没有提到常量表达的元素。

After checking in the draft C99 standard , I would say "no", it's not undefined. 在检查了C99标准草案后,我会说“不”,它不是未定义的。 I can't find any language in the draft that mentions a requirement that iterations end. 我在草案中找不到任何语言提到迭代结束的要求。

The full text of the paragraph describing the semantics of the iterating statements is: 描述迭代语句语义的段落全文如下:

An iteration statement causes a statement called the loop body to be executed repeatedly until the controlling expression compares equal to 0. 迭代语句会导致重复执行一个称为循环体的语句,直到控制表达式比较等于0。

I would expect any limitation such as the one specififed for C++11 to appear there, if applicable. 如果适用,我希望有任何限制,例如为C ++ 11指定的限制。 There is also a section named "Constraints", which also doesn't mention any such constraint. 还有一个名为“约束”的部分,它也没有提到任何这样的约束。

Of course, the actual standard might say something else, although I doubt it. 当然,实际标准可能会说别的,尽管我对此表示怀疑。

The following statement appears in C11 6.8.5 Iteration statements /6 : 以下陈述出现在C11 6.8.5 Iteration statements /6

An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate. 一个迭代语句,其控制表达式不是常量表达式,不执行输入/输出操作,不访问volatile对象,并且不在其主体中执行同步或原子操作,控制表达式,或(在for语句的情况下)它的表达式-3可以由实现假设终止。

Since while(1); 从而while(1); uses a constant expression, the implementation is not allowed to assume it will terminate. 使用常量表达式,不允许实现假定它将终止。

A compiler is free to remove such a loop entirely is the expression is non-constant and all other conditions are similarly met, even if it cannot be proven conclusively that the loop would terminate. 编译器可以完全自由地删除这样的循环,表达式是非常量的,并且所有其他条件都被类似地满足,即使无法确定循环终止也是如此。

The simplest answer involves a quote from §5.1.2.3p6, which states the minimal requirements of a conforming implementation: 最简单的答案涉及§5.1.2.3p6的引用,其中陈述了符合实现的最低要求:

The least requirements on a conforming implementation are: 符合实施的最低要求是:

— Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine. - 严格根据抽象机器的规则评估对易失性对象的访问。

— At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced. - 在程序终止时,写入文件的所有数据应与根据抽象语义产生的程序执行结果相同。

— The input and output dynamics of interactive devices shall take place as specified in 7.21.3. - 交互设备的输入和输出动态应按照7.21.3的规定进行。 The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input. 这些要求的目的是尽快出现无缓冲或行缓冲输出,以确保在程序等待输入之前实际出现提示消息。

This is the observable behavior of the program. 这是该程序的可观察行为。

If the machine code fails to produce the observable behaviour due to optimisations performed, then the compiler isn't a C compiler. 如果机器代码由于执行优化而无法产生可观察行为,则编译器不是C编译器。 What is the observable behaviour of a program that contains only such an infinite loop, at the point of termination? 在终止点上只包含这种无限循环的程序的可观察行为是什么? The only way such a loop could end is by a signal causing it to end prematurely. 这种循环可能结束的唯一方法是通过一个使其过早结束的信号。 In the case of SIGTERM , the program terminates. SIGTERM的情况下,程序终止。 This would cause no observable behaviour. 这不会导致任何可观察到的行为。 Hence, the only valid optimisation of that program is the compiler pre-empting the system closing the program and generating a program that ends immediately. 因此,该程序唯一有效的优化是编译器抢占系统关闭程序并生成立即结束的程序。

/* unoptimised version */
int main() {
    for (;;);
    puts("The loop has ended");
}

/* optimised version */
int main() { }

One possibility is that a signal is raised and longjmp is called to cause execution to jump to a different location. 一种可能性是引发信号并调用longjmp以使执行跳转到不同的位置。 It seems like the only place that could be jumped to is somewhere reached during execution prior to the loop, so providing the compiler is intelligent enough to notice that a signal is raised causing the execution to jump to somewhere else, it could potentially optimise the loop (and the signal raising) away in favour of jumping immediately. 似乎唯一可以跳转到的地方是在循环之前执行期间到达的地方,因此提供编译器足够智能以注意到信号被引发导致执行跳转到其他地方,它可能潜在地优化循环(和信号提升)偏离立即跳跃。

When multiple threads enter the equation, a valid implementation might be able to transfer ownership of the program from the main thread to a different thread, and end the main thread. 当多个线程输入等式时,有效的实现可能能够将程序的所有权从主线程转移到另一个线程,并结束主线程。 The observable behaviour of the program must still be observable, regardless of optimisations. 无论优化如何,程序的可观察行为仍必须是可观察的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM