Are Multiple Mutations of the Same Variable Within Initializer Lists Undefined Behavior Pre C++11

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

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?

Are multiple mutations within initializer lists undefined behavior?

Yes, the code is valid and does not have undefined behavior. Expressions in an initizalizer list are evaluated left-to-right and sequenced before the evaluation of the constructor of S. Therefore, your program should consistently assign value 2 to variable i.

Quoting § 8.5.4 of the C++ Standard:

"Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list."

Thus, what happens is:

  1. ++i gets evaluated, yielding i = 1 (first argument of S's constructor);
  2. ++i gets evaluated, yielding i = 2 (second argument of S's constructor);
  3. S's constructor is executed;
  4. S's conversion operator is executed, returning value 2;
  5. value 2 is assigned to i (which already had value 2).

Another relevant paragraph of the Standard is § 1.9/15, which also mentions similar examples that do have undefined behavior:

i = v[i++]; // the behavior is undefined
i = i++ + 1; // the behavior is undefined

However, the same paragraph says:

"Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] When calling a function (whether or not the function is inline), 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 every expression or statement in the body of the called function."

Since 1) the evaluation of the expressions in the initializer list is sequenced left-to-right, 2) the execution of the constructor of S is sequenced after the evaluation of all expressions in the initializer list, and 3) the assignment to i is sequenced after the execution of the constructor of S (and its conversion operator), the behavior is well-defined.

Incrementing a variable used twice in an initializer list - undefined behavior?

C

In C (not necessarily the same answer as for C++), there are no sequence points associated with the components of an initializer list.

The C11 standard, ISO/IEC 9899:2011, says in section §6.7.9 Initialization:

¶19 The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject; 151)

151) Any initializer for the subobject which is overridden and so not used to initialize that subobject might not be evaluated at all.

That sounds promising, but…

¶23 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.152)

152) In particular, the evaluation order need not be the same as the order of subobject initialization.

So, (in C) the order of evaluation is indeterminately sequenced, and you cannot rely on when the increments occur (or, in extreme cases not illustrated by the code in the question, whether the increments occur).

In C99 (ISO/IEC 9899:1999), the section number is §6.7.8, but paragraphs 19 and 23 have essentially the same content, except that the footnote numbers are different.

In C90 (ISO/IEC 9899:1990), the issue is not addressed explicitly.

C++

Judging from songyuanyao's answer, the rules in C++11 (and later) are different from those in C11. This sort of thing emphasizes that the languages C and C++ are different and makes writing comprehensive answers to questions tagged with both languages extremely difficult.

Closely related questions

There are at least two other questions related to side-effects (such as ++) in contexts other than initializers. They both should be read too. The second, in particular, is of interest to C++ users; the first is tagged C and not C++ and so is of most relevance to those interested in C.

  • Why are these constructs (using ++) undefined behaviour?

  • Undefined behaviour and sequence points

Both were pointed out by πάντα ῥεῖ in the comments.

Order of evaluation in initializer_list c++11

This is one interesting corner of the C++ standard where execution order is well defined. Section 8.5.4 [dcl.init.list], paragraph 4:

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.

So in the initializer list, the function calls are evaluated left-to-right.

Why is this undefined behaviour?

Update 2

So after some research I realized this was actually well defined although the evaluation order is unspecified. It was a pretty interesting putting the pieces together and although there is a more general question covering this for the C++11 case there was not a general question covering the pre C++11 case so I ended up creating a self answer question, Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11 that covers all the details.

Basically, the instinct when seeing makeX(count++), makeX(count++) is to see the whole thing as a full-expression but it is not and therefore each intializer has a sequence point.

Update

As James points out it may not be undefined pre-C++11, which would seem to rely on interpreting the initialization of each element as a full expression but it is not clear you can definitely make that claim.

Original

Pre-C++11 it is undefined behavior to modify a variable more than once within a sequence point, we can see that by looking at the relevant section in an older draft standard would be section 5 Expressions paragraph 4 which says (emphasis mine):

[...]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 the C++11 draft standard this changes and to the following wording from section 1.9 Program execution paragraph 15 says (emphasis mine):

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

and we can see that for initializer lists from section 8.5.4 List-initialization paragraph 4 says:

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.

Interdependent initialization with commas?

The correct answer is that

int x = 42, y = x;

and

int x = 42;
int y = x;

are usually equivalent (not strictly).


Considering the standard § 8 Declarators [dcl.decl]:

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

and in the footnote [100] further explains:

A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single
declarator. That is

T D1, D2, ... Dn;

is usually equivalent to

T D1; T D2; ... T Dn;

where T is a decl-specifier-seq and each Di is an init-declarator.

  • The above guarantees that x = 42 and y = x will be evaluated separately. However, as @Praetorian correctly pointed out in the comments, footnotes are not normative.

  • This means that the order of evaluation is not well defined and an implementer could as well implement the evaluation of the declarations in the reverse order (i.e,. T Dn; ...T D2; T D1;).

  • One might argue that the comma operator is guaranteed left to right evaluation. However, this not the case. According to the K & R [K & R II, 3.6 p.63], that also applies to C++:

The commas that separate function arguments, variables in declarations, etc., are not comma operators, and do not guarantee left to right evaluation.

Pre-Increment Operators when Using the Variable on the Same Line

I would advice against using such "tricks" in your code in the long term this is maintenance nightmare and is hard to reason about. There are almost always alternatives, for example this code:

testStruct test = {a[++i], b[i]}

could be changed to:

++i ;
testStruct test = {a[i], b[i]}

So having said that, neither case uses the comma operator in both functions calls and intialization lists the comma is a grammar elements and nothing else.

Your first situation is well defined although there is some caveats depending on whether this is C++11 or pre C++11.

In both cases there is a sequence point after each comma, although pre C++11 the order of evaluation is not specified. So we can see this for the pre C++11 case by going to defect report 430 which says:

A recent GCC bug report (
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11633) asks about the
validity of

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

is this undefined or unspecified or something else?

and the answer is (emphasis mine going forward):

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?

In C++11 it is baked in the draft C++11 standard in section 8.4.5 paragraph which says:

Within the initializer-list of a braced-init-list, the
initializer-clauses, including any that result from pack expansions
(14.5.3), are evaluated in the order in which they appear. That is,
every value computation and side effect associated with a given
initializer-clause is sequenced before every value computation and
side effect
associated with any initializer-clause that follows it in
the comma-separated list of the initializer-list.

I am sticking with C++11 going forward since it does not change the answer for the rest of the content, although the wording on sequencing does vary the conclusion is the same.

The second situation invokes undefined behavior since the order of evaluation of arguments to a function are unspecified and their evaluation is indeterminately sequenced with respect to one another. We can see this undefined behavior from section 1.9 paragraph 15 which says:

Except where noted, evaluations of operands of individual operators
and of subexpressions of individual expressions are unsequenced. [
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. If a side effect on a scalar object is
unsequenced relative to either another side effect on the same scalar
object or a value computation using the value of the same scalar
object, the behavior is undefined
.

and provides the following example:

f(i = -1, i = -1); // the behavior is undefined

std::initializer_list and order of evaluation of the elements

According to C++11 § 8.5.4 [dcl.init.list] paragraph 4:

4 Within the initializer-list of a braced-init-list, the
initializer-clauses, including any that result from pack expansions
(14.5.3), are evaluated in the order in which they appear. That is,
every value computation and side effect associated with a given
initializer-clause is sequenced before every value computation and
side effect associated with any initializer-clause that follows it in
the comma-separated list of the initializer-list.

As far as I know GCC 4.8.1 has a bug relative to evaluation of initializers. I described it here

http://cpp.forum24.ru/?1-3-0-00000063-000-0-0-1378892425

Though the text is written in Russion but it can be simply translated in English by using for example google translate.



Related Topics



Leave a reply



Submit