Sequence point

Revision as of 00:27, 13 March 2025 by imported>SamB (Undid revision 1167955532 by Matthiaspaul (talk))
(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)

Template:Short description In C and C++, a sequence point defines any point in a computer program's execution at which it is guaranteed that all side effects of previous evaluations will have been performed, and no side effects from subsequent evaluations have yet been performed. They are a core concept for determining the validity of and, if valid, the possible results of expressions. Adding more sequence points is sometimes necessary to make an expression defined and to ensure a single valid order of evaluation.

With C11 and C++11, usage of the term sequence point has been replaced by sequencing. There are three possibilities:<ref>{{#invoke:citation/CS1|citation |CitationClass=web }}</ref><ref>{{#invoke:citation/CS1|citation |CitationClass=web }}</ref><ref>{{#invoke:citation/CS1|citation |CitationClass=web }}</ref>

  1. An expression's evaluation can be sequenced before that of another expression, or equivalently the other expression's evaluation is sequenced after that of the first.
  2. The expressions' evaluation is indeterminately sequenced, meaning one is sequenced before the other, but which is unspecified.
  3. The expressions' evaluation is unsequenced.

The execution of unsequenced evaluations can overlap, leading to potentially catastrophic undefined behavior if they share state. This situation can arise in parallel computations, causing race conditions, but undefined behavior can also result in single-threaded situations. For example, a[i] = i++; (where <syntaxhighlight lang="text" class="" style="" inline="1">a</syntaxhighlight> is an array and <syntaxhighlight lang="text" class="" style="" inline="1">i</syntaxhighlight> is an integer) has undefined behavior.

Examples of ambiguityEdit

Consider two functions f() and g(). In C and C++, the + operator is not associated with a sequence point, and therefore in the expression f()+g() it is possible that either f() or g() will be executed first. The comma operator introduces a sequence point, and therefore in the code f(),g() the order of evaluation is defined: first f() is called, and then g() is called.

Sequence points also come into play when the same variable is modified more than once within a single expression. An often-cited example is the C expression i=i++, which apparently both assigns i its previous value and increments i. The final value of i is ambiguous, because, depending on the order of expression evaluation, the increment may occur before, after, or interleaved with the assignment. The definition of a particular language might specify one of the possible behaviors or simply say the behavior is undefined. In C and C++, evaluating such an expression yields undefined behavior.<ref>Clause 6.5#2 of the C99 specification: "Between the previous and next sequence point an 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."</ref> Other languages, such as C#, define the precedence of the assignment and increment operator in such a way that the result of the expression i=i++ is guaranteed.

BehaviorEdit

Up to C++03Edit

In C<ref>Annex C of the C99 specification lists the circumstances under which a sequence point may be assumed.</ref> and C++,<ref>The 1998 C++ standard lists sequence points for that language in section 1.9, paragraphs 16–18.</ref> sequence points occur in the following places. (In C++, overloaded operators act like functions, and thus operators that have been overloaded introduce sequence points in the same way as function calls.)

  1. Between evaluation of the left and right operands of the && (logical AND), || (logical OR) (as part of short-circuit evaluation), and comma operators. For example, in the expression <syntaxhighlight lang="c" class="" style="" inline="1">*p++ != 0 && *q++ != 0</syntaxhighlight>, all side effects of the sub-expression <syntaxhighlight lang="c" class="" style="" inline="1">*p++ != 0</syntaxhighlight> are completed before any attempt to access <syntaxhighlight lang="c" class="" style="" inline="1">q</syntaxhighlight>.
  2. Between the evaluation of the first operand of the ternary conditional operator and its second or third operand. For example, in the expression <syntaxhighlight lang="c" class="" style="" inline="1">a = (*p++) ? (*p++) : 0</syntaxhighlight> there is a sequence point after the first <syntaxhighlight lang="c" class="" style="" inline="1">*p++</syntaxhighlight>, meaning it has already been incremented by the time the second instance is executed.
  3. At the end of a full expression. This category includes expression statements (such as the assignment <syntaxhighlight lang="c" class="" style="" inline="1">a=b;</syntaxhighlight>), return statements, the controlling expressions of <syntaxhighlight lang="c" class="" style="" inline="1">if</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">switch</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">while</syntaxhighlight>, or <syntaxhighlight lang="c" class="" style="" inline="1">do</syntaxhighlight>-<syntaxhighlight lang="c" class="" style="" inline="1">while</syntaxhighlight> statements, and each of the three expressions in a for statement.
  4. Before a function is entered in a function call. The order in which the arguments are evaluated is not specified, but this sequence point means that all of their side effects are complete before the function is entered. In the expression <syntaxhighlight lang="c" class="" style="" inline="1">f(i++) + g(j++) + h(k++)</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">f</syntaxhighlight> is called with a parameter of the original value of <syntaxhighlight lang="c" class="" style="" inline="1">i</syntaxhighlight>, but <syntaxhighlight lang="c" class="" style="" inline="1">i</syntaxhighlight> is incremented before entering the body of <syntaxhighlight lang="c" class="" style="" inline="1">f</syntaxhighlight>. Similarly, <syntaxhighlight lang="c" class="" style="" inline="1">j</syntaxhighlight> and <syntaxhighlight lang="c" class="" style="" inline="1">k</syntaxhighlight> are updated before entering <syntaxhighlight lang="c" class="" style="" inline="1">g</syntaxhighlight> and <syntaxhighlight lang="c" class="" style="" inline="1">h</syntaxhighlight> respectively. However, it is not specified in which order <syntaxhighlight lang="c" class="" style="" inline="1">f()</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">g()</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">h()</syntaxhighlight> are executed, nor in which order <syntaxhighlight lang="c" class="" style="" inline="1">i</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">j</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">k</syntaxhighlight> are incremented. If the body of <syntaxhighlight lang="c" class="" style="" inline="1">f</syntaxhighlight> accesses the variables <syntaxhighlight lang="c" class="" style="" inline="1">j</syntaxhighlight> and <syntaxhighlight lang="c" class="" style="" inline="1">k</syntaxhighlight>, it might find both, neither, or just one of them to have been incremented. (The function call <syntaxhighlight lang="c" class="" style="" inline="1">f(a,b,c)</syntaxhighlight> is not a use of the comma operator; the order of evaluation for <syntaxhighlight lang="c" class="" style="" inline="1">a</syntaxhighlight>, <syntaxhighlight lang="c" class="" style="" inline="1">b</syntaxhighlight>, and <syntaxhighlight lang="c" class="" style="" inline="1">c</syntaxhighlight> is unspecified.)
  5. At a function return, after the return value is copied into the calling context. (This sequence point is only specified in the C++ standard; it is present only implicitly in C.<ref>C++ standard, ISO 14882:2003, section 1.9, footnote 11.</ref>)
  6. At the end of an initializer; for example, after the evaluation of <syntaxhighlight lang="c" class="" style="" inline="1">5</syntaxhighlight> in the declaration <syntaxhighlight lang="c" class="" style="" inline="1">int a = 5;</syntaxhighlight>.
  7. Between each declarator in each declarator sequence; for example, between the two evaluations of <syntaxhighlight lang="c" class="" style="" inline="1">a++</syntaxhighlight> in <syntaxhighlight lang="c" class="" style="" inline="1">int x = a++, y = a++</syntaxhighlight>.<ref>C++ standard, ISO 14882:2003, section 8.3: "Each init-declarator in a declaration is analyzed separately as if it was in a declaration by itself."</ref> (This is not an example of the comma operator.)
  8. After each conversion associated with an input/output format specifier. For example, in the expression <syntaxhighlight lang="c" class="" style="" inline="1">printf("foo %n %d", &a, 42)</syntaxhighlight>, there is a sequence point after the <syntaxhighlight lang="text" class="" style="" inline="1">%n</syntaxhighlight> is evaluated and before printing <syntaxhighlight lang="text" class="" style="" inline="1">42</syntaxhighlight>.

C11 and C++11Edit

Template:Section expand

Partially because of the introduction of language support for threads, C11 and C++11 introduced new terminology for evaluation order. An operation may be "sequenced before" another, or the two can be "indeterminately" sequenced (one must complete before the other) or "unsequenced" (the operations in each expression may be interleaved).

C++17Edit

C++17 restricted several aspects of evaluation order. The <syntaxhighlight lang="text" class="" style="" inline="1">new</syntaxhighlight> expression will always perform the memory allocation before evaluating the constructor arguments. The operators <syntaxhighlight lang="text" class="" style="" inline="1"><<</syntaxhighlight>, <syntaxhighlight lang="text" class="" style="" inline="1">>></syntaxhighlight>, <syntaxhighlight lang="text" class="" style="" inline="1">.</syntaxhighlight>, <syntaxhighlight lang="text" class="" style="" inline="1">.*</syntaxhighlight>, <syntaxhighlight lang="text" class="" style="" inline="1">->*</syntaxhighlight>, and the subscript and function call operator are guaranteed to be evaluated left to right (whether they are overloaded or not). For example, the code

<syntaxhighlight lang="cpp"> std::cout << a() << b() << c(); // parsed as (((std::cout << a()) << b()) << c()); </syntaxhighlight>

is newly guaranteed to call <syntaxhighlight lang="text" class="" style="" inline="1">a</syntaxhighlight>, <syntaxhighlight lang="text" class="" style="" inline="1">b</syntaxhighlight> and <syntaxhighlight lang="text" class="" style="" inline="1">c</syntaxhighlight> in that order. The right-hand side of any assignment-like operator is evaluated before the left-hand side, so that b() *= a(); is guaranteed to evaluate <syntaxhighlight lang="text" class="" style="" inline="1">a</syntaxhighlight> first. Finally, although the order in which function parameters are evaluated remains implementation-defined, the compiler is no longer allowed to interleave sub-expressions across multiple parameters.<ref>{{#invoke:citation/CS1|citation |CitationClass=web }}</ref>

ReferencesEdit

<references/>

External linksEdit