Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11
Asked Answered
P

1

5

Consider the following code:

int main()
{
    int count = 0 ;
    int arrInt[2] = { count++, count++ } ;

    return 0 ;
}

If we compile the code using clang -std=c++03 it produces the following warning(live example):

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]
    int arrInt[2] = { count++, count++ } ;
                           ^        ~~

I am not advocating for code like this but similar code came up in another question and there was disagreement over whether it is defined or not according to the standard pre-C++11. In C++11 this behavior is well defined behavior according to Are multiple mutations within initializer lists undefined behavior and indeed if I use -std=c++11 then the warning goes away.

If we look at a pre-C++11 draft standard it does not have the same language covering initializer-list so it seems we are left with Chapter 5 Expressions paragraph 4 which says:

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.57) 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.

In order for this to be undefined it would seem we would have to interpret count++, count++ as an expression and therefore each count++ as a subexpression, so is this code undefined pre-C++11?

Pandean answered 9/11, 2013 at 18:59 Comment(0)
P
4

The code is not undefined pre-C++11 but the evaluation order is unspecified. If we look at the draft standard section 1.9 Program execution paragraph 12 says:

A full-expression is an expression that is not a subexpression of another expression. [...]

and paragraph 15 says:

There is a sequence point at the completion of evaluation of each full-expression12).

then the question is whether count++, count++ is a full expression and each count++ a sub-expression or is each count++ it's own full expression and therefore there is sequence point after each one? if we look at the grammar for this initialization from section 8.5 Initializers:

initializer-clause:
  assignment-expression
  { initializer-list ,opt }
  { }
initializer-list:
  initializer-clause
  initializer-list , initializer-clause

the only expression we have is an assignment-expression and the , separating the components is part of the initializer-list and and not part of an expression and therefore each count++ is a full expression and there is a sequence point after each one.

This interpretation is confirmed by the following gcc bug report, which has very similar code to mine(I came up with my example way before I found this bug report):

int count = 23;
int foo[] = { count++, count++, count++ };

which ends up as defect report 430, which I will quote:

[...]I believe the standard is clear that each initializer expression in the above is a full-expression (1.9 [intro.execution]/12-13; see also issue 392) and therefore there is a sequence point after each expression (1.9 [intro.execution]/16). I agree that the standard does not seem to dictate the order in which the expressions are evaluated, and perhaps it should. Does anyone know of a compiler that would not evaluate the expressions left to right?

Pandean answered 9/11, 2013 at 18:59 Comment(2)
Interesting. It would seem that an essential point is that expressions in a true initializer-list are considered full-expressions because one cannot find any larger structure with 'expression' in its name that contains them. This is relevant because syntactically initializer-list is also used for an expression-list, which also occurs in for instance function calls. There the is an enclosing expression (the postfix-expression for the full function call), so that individual arguments are not considered full-expressions, and there are no sequence points after them.Feathery
@MarcvanLeeuwen that is correct, I always meant to add a section explaining why this was different than a function call. I am glad that you were able to figure that out yourself, that is always a rewarding experience.Pandean

© 2022 - 2024 — McMap. All rights reserved.