What exactly is the "as-if" rule?
As the title says:
What exactly is the "as-if" rule?
A typical answer one would get is:
The rule that allows any and all code transformations that do not change the observable behavior of the program
From time to time, we keep getting behaviors from certain implementations, which are attributed to this rule. Many times wrongly.
So, what exactly is this rule? The standard does not clearly mention this rule as a section or paragraph, so what exactly falls under the purview of this rule?
To me, it seems like a grey area which is not defined in detail by the standard. Can someone elaborate on the details, citing the references from the standard?
Note: Tagging this as C and C++ both, because it is relevant to both languages.
Solution 1:
What is the "as-if" rule?
The "as-if" rule basically defines what transformations an implementation is allowed to perform on a legal C++ program. In short, all transformations that do not affect a program's "observable behavior" (see below for a precise definition) are allowed.
The goal is to give implementations freedom to perform optimizations as long as the behavior of the program remains compliant with the semantics specified by the C++ Standard in terms of an abstract machine.
Where does the Standard introduce this rule?
The C++11 Standard introduces the "as-if" rule in Paragraph 1.9/1:
The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. This International Standard places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.
Also, an explanatory footnote adds:
This provision is sometimes called the “as-if” rule, because an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. For instance, an actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no side effects affecting the observable behavior of the program are produced.
What does the rule mandate exactly?
Paragraph 1.9/5 further specifies:
A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).
It is worth stressing that this constraint applies when "executing a well-formed program" only, and that the possible outcomes of executing a program which contains undefined behavior are unconstrained. This is made explicit in Paragraph 1.9/4 as well:
Certain other operations are described in this International Standard as undefined (for example, the effect of attempting to modify a const object). [ Note: This International Standard imposes no requirements on the behavior of programs that contain undefined behavior. —end note ]
Finally, concerning the definition of "observable behavior", Paragraph 1.9/8 goes as follows:
The least requirements on a conforming implementation are:
— Access 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 one of the possible results that execution of the program according to the abstract semantics would have produced.
— The input and output dynamics of interactive devices shall take place in such a fashion that prompting output is actually delivered before a program waits for input. What constitutes an interactive device is implementation-defined.
These collectively are referred to as the observable behavior of the program. [ Note: More stringent correspondences between abstract and actual semantics may be defined by each implementation. —end note ]
Are there situations where this rule does not apply?
To the best of my knowledge, the only exception to the "as-if" rule is copy/move elision, which is allowed even though the copy constructor, move constructor, or destructor of a class have side effects. The exact conditions for this are specified in Paragraph 12.8/31:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [...]
Solution 2:
In C11 the rule is never called by that name. However C, just like C++, defines the behaviour in terms of abstract machine. The as-if rule is in C11 5.1.2.3p4 and p6:
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
[...]
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. 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.