Doc. No.: WG21/N2239
J16/07-0099
Date: 2007-04-19
Reply to: Clark Nelson
Phone: +1-503-712-8433
Email: clark.nelson@intel.com

A finer-grained alternative to sequence points (revised)

This document contains the proposed working draft changes from N2171 which are not specific to parallel execution; i.e. just the reformulation of the "sequence point" rules.

Rearranging the text of "Program execution"

The changes proposed in N1944 were mainly in section 1.9 (Program execution) and various locations in clause 5 (Expressions), plus a couple of spots in clause 12 (Special member functions). The "undefined behavior" rule, a key paragraph in the understanding of sequencing, which basically describes what may be called an "intra-thread data race", is currently in 5p4, which is widely separated from the bulk of the discussion of the principles of sequencing in 1.9. Furthermore, it would seem logical to describe concurrency — and particularly inter-thread data races — in a new section building on and immediately following 1.9. Therefore we propose to move the "undefined behavior" rule from 5p4 to 1.9.

Within 1.9 with the changes proposed in N1944, the bulk of the discussion of sequencing is in p15-16. Paragraph 8, which currently contains the "no overlap" rule for function execution, should be merged into p16, which discusses many other sequencing constraints on function calls. And if, as proposed, the references to sequence points and evaluation are removed from p11 (the "least requirements"), then the definitions in p7 are not needed until p15; moving paragraph 7 down would result in a more cohesive presentation.

Finally, it could be argued that cohesiveness would be increased still further by moving the discussion of reassociation (concerning implications of the "as-if" rule) to immediately follow the "least requirements" (which is basically the normative statement of the "as-if" rule), instead of showing up in the middle of the discussion of expressions and sequencing.

This table shows the proposed shifting of content, assuming regular paragraph (re-)numbering. The letters in the central columns are just tags, intended to illustrate how text moves around (in lieu of arrows): the tag stays with the content.

Paragraph number Old content New content
1.9p7 Definitions of "side effect", "sequence point" A C Effect of asynchronous signal
1.9p8 "No overlap" rule for function execution B C Allocation of automatic objects
1.9p9 Effect of asynchronous signal C C The "least requirements"
1.9p10 Allocation of automatic objects C E Note concerning reassociation
1.9p11 The "least requirements" C D Definition of "full-expression"
1.9p12 Definition of "full-expression" D D Note concerning default arguments
1.9p13 Note concerning default arguments D A Definition of "side effect", "evaluation"
1.9p14 Note concerning reassociation E [new] Definition of "sequenced before"
1.9p15 Sequencing between full-expressions F F Sequencing between full-expressions
1.9p16 Sequencing constraints on function calls G 5p4 The "undefined behavior" rule
1.9p17 Operators that impose a sequence point [delete] G+B Sequencing constraints on function calls, including the "no overlap" rule

The text proposed for "Program execution"

So here is the proposed reading of section 1.9, beginning with p6 (just for the sake of context). Each paragraph is introduced with its proposed paragraph number, and an explanation of its source. Text from the current working draft to be replaced or deleted is stricken through. Replacement or added text is underlined. Footnotes are presented here in the same style as examples and notes. If the introductory paragraphs, editorial notes and stricken text were deleted, the result would be a longish block of consecutive paragraphs, as proposed for the standard.

1.9p6 (unchanged):

The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions. An implementation can offer additional library I/O functions as an extension. [ Footnote: Implementations that do so should treat calls to those functions as "observable behavior" as well. —end footnote ]

Editorial note: This definition of observable behavior is not clearly consistent with the "least requirements" described in the proposed 1.9p9 below (and is arguably incorrect, especially for multithreaded programs). Core issue 612 has been opened to consider this inconsistency, and any corrections necessary for multithreading will be drafted in accordance with its resolution.

1.9p7 (unchanged from the current p9, except for the addition of an omitted word):

When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects with type other than volatile std::sig_atomic_t are unspecified, and the value of any object not of type volatile std::sig_atomic_t that is modified by the handler becomes undefined.

1.9p8 (unchanged from the current p10):

An instance of each object with automatic storage duration (3.7.2) is associated with each entry into its block. Such an object exists and retains its last-stored value during the execution of the block and while the block is suspended (by a call of a function or receipt of a signal).

1.9p9 (original text from p11):

The least requirements on a conforming implementation are:

[ Note: more stringent correspondences between abstract and actual semantics may be defined by each implementation. —end note ]

1.9p10 (unchanged from p14):

[ Note: operators can be regrouped according to the usual mathematical rules only where the operators really are associative or commutative.11) For example, in the following fragment

[unchanged text omitted]

However on a machine in which overflows do not produce an exception and in which the results of overflows are reversible, the above expression statement can be rewritten by the implementation in any of the above ways because the same result will occur. —end note ]

1.9p11 (original text from p12):

A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition. A call to a destructor generated at the end of the lifetime of an object other than a temporary object is an implicit full-expression. Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression. [ Example:

[unchanged example omitted]

1.9p12 (unchanged from p13):

[ Note: the evaluation of a full-expression can include the evaluation of subexpressions that are not lexically part of the full-expression. For example, subexpressions involved in evaluating default argument expressions (8.3.6) are considered to be created in the expression that calls the function, not the expression that defines the default argument. —end note ]

1.9p13 (original text from p7):

Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression might produce side effects. Evaluation of an expression (or sub-expression) in general includes both value computations (including determining the identity of an object for lvalue evaluation, and fetching a value previously assigned to an object for rvalue evaluation) and initiation of side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place. [ Footnote: Note that some aspects of sequencing in the abstract machine are unspecified; the preceding restriction upon side effects applies to that particular execution sequence in which the actual code is generated. Also note that when a call to a library I/O function returns, or an access to a volatile object is evaluated, the side effect is considered complete, even though some external actions implied by either the call (such as the I/O itself) or the volatile access may not have completed yet. end footnote ]

Note that what was a footnote has been modified and incorporated into the normative text.

1.9p14 (new paragraph):

"Sequenced before" is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread, which induces a partial order among those evaluations. Given any two evaluations A and B, if A is sequenced before B, then the execution of A shall precede the execution of B. If A is not sequenced before B and B is not sequenced before A, then A and B are unsequenced. [ Note: The execution of unsequenced evaluations can overlap. —end note ] Evaluations A and B are indeterminately sequenced when either A is sequenced before B, or B is sequenced before A, but it is unspecified which. [ Note: Indeterminately sequenced evaluations shall not overlap, but either could be executed first. —end note ]

1.9p15 (original text from p15):

There is a sequence point at the completion of evaluation of each full-expression. Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated. [ Footnote: As specified in 12.2, after the "end-of-full-expression" sequence point after a full-expression is evaluated, a sequence of zero or more invocations of destructor functions for temporary objects takes place, usually in reverse order of the construction of each temporary object. —end footnote ]

1.9p16 (original text from clause 5 paragraph 4):

Except where noted, the order of evaluation evaluations of operands of individual operators, and of subexpressions of individual expressions, and the order in which side effects take place, is unspecified are unsequenced. [ Footnote: The precedence of operators is not directly specified, but it can be derived from the syntax. —end footnote ] [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. —end note ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object, or a value computation using the value of the same scalar object, the behavior is undefined. [ Example:

i = v[i++];       // the behavior is undefined
i = 7, i++, i++;  // i becomes 9
i = ++i + 1;      // the behavior is undefined
i = i + 1;        // the value of i is incremented

end example ]

Editorial note: It has been pointed out that, under this proposed wording, unsequenced read accesses to a single volatile object (clearly) entail undefined behavior, which was not clearly the case with the previous wording. The key difference is that the new words refer to a "side effect", which definitely includes reading a volatile object, whereas the previous words referred to modifying an object "by the evaluation of an expression", which is ambiguous with respect to reading a volatile object — since such an action is a side effect, modification of the object accessed (or of some other volatile object) is possible but not inevitable.

1.9p17 (original text is p16 with p8 inserted):

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of any expressions or statements every expression or statement in the body of the called function body. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ] There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function. [ Footnote: The sequence point at the function return is not explicitly specified in ISO C, and can be considered redundant with sequence points at full-expressions, but the extra clarity is important in C++. In C++, there are more ways in which a called function can terminate its execution, such as the throw of an exception. —end footnote ] Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function. [ Footnote: In other words, function executions do not "interleave" with each other. —end footnote ] Several contexts in C++ cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit. [ Example: evaluation of a new expression invokes one or more allocation and constructor functions; see 5.3.4. For another example, invocation of a conversion function (12.3.2) can arise in contexts in which no function call syntax appears. —end example ] The sequence points at function-entry and function-exit sequencing constraints on the execution of the called function (as described above) are features of the function calls as evaluated, whatever the syntax of the expression that calls the function might be.

Deleted as redundant with descriptions of operators (original text from p17):

In the evaluation of each of the expressions

a && b
a || b
a ? b : c
a , b

using the built-in meaning of the operators in these expressions (5.14, 5.15, 5.16, 5.18), there is a sequence point after the evaluation of the first expression. [ Footnote: The operators indicated in this paragraph are the built-in operators, as described in clause 5. When one of these operators is overloaded (clause 13) in a valid context, thus designating a user-defined operator function, the expression designates a function invocation, and the operands form an argument list, without an implied sequence point between them. —end footnote ]

Sequencing for specific operators

It seems appropriate to remind the reader, at this point in the paper, that the proposal is to move 5p4 from its current location.

5.2.2p8 (function call):

[ Note: The order of evaluation of arguments is unspecified. The evaluations of the postfix expression and of the argument expressions are all unsequenced relative to one another. All side effects of argument expression evaluations take effect are sequenced before the function is entered; see ([intro.execution]=1.9). The order of evaluation of the postfix expression and the argument expression list is unspecified. end note ]

5.2.6p1 (post-increment):

The value obtained by applying of a postfix ++ expression is the value that the of its operand had before applying the operator. [ Note: the value obtained is a copy of the original value —end note ] The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type or a pointer to a complete object type. After the result is noted, the The value of the operand object is modified by adding 1 to it, unless the object is of type bool, in which case it is set to true. [ Note: this use is deprecated, see Annex D. —end note ] The value computation of the ++ expression is sequenced before the modification of the operand object. With respect to an indeterminately-sequenced function call, the operation of postfix ++ is a single evaluation. [ Note: Therefore, a function call shall not intervene between the lvalue-to-rvalue conversion and the side effect associated with any single postfix ++ operator. —end note ] The result is an rvalue. The type of the result is the cv-unqualified version of the type of the operand. See also 5.7 and 5.17.

5.14p2 (logical AND operator), and also 5.15p2 (logical OR operator):

The result is a bool. All side effects of the first expression except for destruction of temporaries (12.2) happen before the second expression is evaluated. If the second expression is evaluated, every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second expression.

5.16p1 (conditional operator):

Conditional expressions group right-to-left. The first expression is implicitly converted to bool (clause 4). It is evaluated and if it is true, the result of the conditional expression is the value of the second expression, otherwise that of the third expression. All side effects of the first expression except for destruction of temporaries (12.2) happen before the second or third expression is evaluated. Only one of the second and third expressions is evaluated. Every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second or third expression.

5.17p1 (assignment and compound assignment operators):

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. [ Note: Therefore, a function call shall not intervene between the lvalue-to-rvalue conversion and the side effect associated with any single compound assignment operator. —end note ]

5.18p1 (comma operator):

A pair of expressions separated by a comma is evaluated left-to-right and the value of the left expression is discarded. The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are not applied to the left expression. All side effects (1.9) of the left expression, except for the destruction of temporaries (12.2), are performed before the evaluation of the right expression. Every value computation and side effect associated with the left expression is sequenced before every value computation and side effect associated with the right expression. The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is an lvalue, and is a bit-field if its right operand is an lvalue and a bit-field.

Sequencing for destruction of temporaries

12.2p3:

When an implementation introduces a temporary object of a class that has a non-trivial constructor (12.1, 12.8), it shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for a temporary with a non-trivial destructor (12.4). Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception. The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.

12.2p4:

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, any temporaries the destruction of every temporary created in the a default argument expressions are destroyed immediately after return from the constructor expression is sequenced before the construction of the next array element, if any.

12.2p5:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits. A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call. A temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits. In all these cases, the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction. The destruction of a temporary whose lifetime is not extended by being bound to a reference is sequenced before the destruction of every temporary which is constructed earlier in the same full-expression. If the lifetime of two or more temporaries to which references are bound ends at the same point, these temporaries are destroyed at that point in the reverse order of the completion of their construction. In addition, the destruction of temporaries bound to references shall take into account the ordering of destruction of objects with static or automatic storage duration (3.7.1, 3.7.2); that is, if obj1 is an object with the same storage duration as the temporary and created before the temporary is created the temporary shall be destroyed before obj1 is destroyed; if obj2 is an object with the same storage duration as the temporary and created after the temporary is created the temporary shall be destroyed after obj2 is destroyed. [ Example:

Fixes for miscellaneous sequencing issues

3.6.2p1 (initialization of non-local objects):

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of POD type with static storage duration can be initialized with a constant expression (5.19); this is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units. An unordered initialization is indeterminately sequenced with respect to every other dynamic initialization. [ Note: 8.5.1 describes the order in which aggregate members are initialized. The initialization of local static objects is described in 6.7. —end note ]

8.5.1p17 (aggregate initialization); new paragraph:

The full-expressions in an initializer-clause are evaluated in the order in which they appear.

12.6.2p3 (mem-initializers):

The expression-list in a mem-initializer is used to initialize the base class or non-static data member subobject denoted by the mem-initializer-id. The semantics of a mem-initializer are as follows:

[unchanged example omitted]

There is a sequence point (1.9) after the initialization of each base and member. The initialization of each base and member constitutes a full-expression. The expression-list of Any expression in a mem-initializer is evaluated as part of the initialization of the corresponding base or member full-expression that performs the initialization.

14.2 (template arguments):

template-argument:
assignment-expression constant-expression
type-id
id-expression