clanguage-lawyerundefined-behaviorsequence-pointsc17

Does the definition int a = 0, b = a++, c = a++; have defined behavior in C?


Does the definition int a = 0, b = a++, c = a++; have defined behavior in C?

Or almost equivalently, does the comma in an object definition introduce a sequence point as for the comma operator in expressions?

Similar questions have been asked for C++:

The widely accepted answer for C++ is Yes, it is fully defined per paragraph 8/3 of the C++11 Standard:

Each init-declarator in a declaration is analyzed separately as if it was in a declaration by itself

Albeit this paragraph only refers to the syntax analysis phase and is not quite precise enough regarding the sequencing of operations at runtime.

What is the situation for the C language? Does the C Standard define the behavior?

A similar question was asked before:

Does the comma in a declaration for multiple objects introduce a sequence point like the comma operator?

Yet the answer seems to refer specifically to the C11 draft and may not hold for more recent versions of the C Standard as the wording of the informative Annex C has changed since the C11 draft and does not seem fully consistent with the Standard text either.

EDIT: of course such an initializer seems uselessly contorted. I definitely do not condone such programming style and constructions. The question arose from a discussion regarding a trivial definition: int res = 0, a = res; for which the behavior did not seem fully defined (!). Initializers with side effects are not so uncommon, consider for example this one: int arg1 = pop(), arg2 = pop();


Solution

  • Does the definition int a = 0, b = a++, c = a++; have defined behavior in C?

    Yes, because C 2018 6.8 3 says these initializations (not all, see bottom) are evaluated in the order they appear:

    … The initializers of objects that have automatic storage duration, and the variable length array declarators of ordinary identifiers with block scope, are evaluated and the values are stored in the objects (including storing an indeterminate value in objects without an initializer) each time the declaration is reached in the order of execution, as if it were a statement, and within each declaration in the order that declarators appear. [Emphasis added.]

    Also, 6.8 4 tells us that each initializer is a full expression and there is a sequence point after the evaluation of a full expression and evaluation of the next:

    A full expression is an expression that is not part of another expression, nor part of a declarator or abstract declarator. There is also an implicit full expression in which the non-constant size expressions for a variably modified type are evaluated; within that full expression, the evaluation of different size expressions are unsequenced with respect to one another. There is a sequence point between the evaluation of a full expression and the evaluation of the next full expression to be evaluated.

    Given both the above, the initializers are sequenced in the order they appear. a is initialized first and so has a value when a++ is evaluated for b, and the side effects for that are completed before the a++ for c begins, so the whole declaration is safe from the “unsequenced effects” rule in 6.5 2.

    6.8 3 is a bit lacking for two reasons:

    Also note that not all initializers are evaluated in the order they appear in a declaration. 6.7.9 23 discusses initializers for aggregates and unions and says:

    The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified.

    History

    The wording in 6.8 3 quoted above goes back to C 1999. In C 1990, it had this form in 6.6.2, which is about compound statements:

    … The initializers of objects that have automatic storage duration are evaluated and the values are stored in the objects in the order their declarators appear in the translation unit.