2

Here's the sample code:

X * makeX(int index) { return new X(index); }
struct Tmp {
    mutable int count;
    Tmp() : count(0) {}
    const X ** getX() const { 
        static const X* x[] = { makeX(count++), makeX(count++) };
        return x; 
    }
};

This reports Undefined Behavior on CLang build 500 in the static array construction. For sake of simplification for this post, the count is not static, but it does not change anything. The error I am receiving is as follows:

test.cpp:8:44: warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
xryl669
  • 3,376
  • 24
  • 47
  • Modifying count twice without a sequence point? – Oliver Charlesworth Nov 08 '13 at 14:56
  • @ShafikYaghmour, yes, I adapted a larger code to a simple question, as I said, the original code is using a static count. I've edited the question. – xryl669 Nov 08 '13 at 15:08
  • @xryl669 Ok, so it's the "version number". But which version number? I know the marketing version number (currently 3.4) and the subversion tag / revision number (currently about 180,000). How did you get this number? – dyp Nov 08 '13 at 15:12
  • @ShafikYaghmour: Compiled this test code, providing a default implementation to X, it gives: "test.cpp:8:44: warning: multiple unsequenced modifications to 'count' [-Wunsequenced]" – xryl669 Nov 08 '13 at 16:03
  • @xryl669 I updated my answer, I ended up creating a self answer question that after a long amount of research into this topic. – Shafik Yaghmour Nov 15 '13 at 18:19

3 Answers3

10

In C++11, this is fine; each clause of an initialiser list is sequenced before the next one, so the evaluation is well-defined.

Historically, the clauses might have been unsequenced, so the two unsequenced modifications of count would give undefined behaviour.

(Although, as noted in the comments, it might have been well-defined even then - you can probably interpret the standard as implying that each clause is a full-expression, and there's a seqeuence point at the end of each full-expression. I'll leave it to historians to debate the finer points of obsolete languages.)

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • You are right, I've just tried with -std=c++11 and the warning vanished. – xryl669 Nov 08 '13 at 15:04
  • Each initializer is a "full expression". Pre C++11, there was a sequence point at the end of each full expression. There was no undefined behavior here in pre-C++11, either. – James Kanze Nov 08 '13 at 15:27
  • @JamesKanze: It's not at all clear to me that they're *full-expressions*, since they're part of a larger expression. On the other hand, I don't make a habit of understanding the finer details of obsolete languages, so maybe both Clang and I missed something. – Mike Seymour Nov 08 '13 at 15:40
  • What larger expression? They're part of an _initializer-clause_, which isn't an expression. – James Kanze Nov 08 '13 at 16:06
  • @JamesKanze: Then I guess that's probably what I was missing; but I can't be bothered to rummage around in my old copy of C++03 to make sure. – Mike Seymour Nov 08 '13 at 16:18
  • @JamesKanze I agree with you now but it took me a bit of work to prove it to myself and eventually found a defect report which cover this exact issue. I turned my research into a self answered question [here](http://stackoverflow.com/questions/19881803/are-multiple-mutations-within-initializer-lists-undefined-behavior-pre-c11) let me know if you feel I missed any details. – Shafik Yaghmour Nov 09 '13 at 21:03
2

Because it this case, the , is NOT a sequence point, but acts more like a delimiter in the initialization of the elements of the array.

In other words, you're modifying the same variable twice in a statement without sequence points (between the modifications).


EDIT: thanks to @MikeSeymour: this is an issue in C++03 an before. It seems like in C++11, the order of evaluation is defined for this case.

Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
2

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.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • The problem is that his code doesn't modify the variable twice without an intervening sequence point. (See §1.9/12: "A full-expression is an expression that is not a subexpression of another expression" and §1.9/16: "There is a sequence point at the completion of evaluation of each full-expression".) – James Kanze Nov 08 '13 at 15:36
  • The function calls do _not_ introduce a sequence point between the `++` operators, but that's irrelevant. Each initializer is a full expression, and there is a sequence point between each full expression. (I can't find where the order of initialization is guaranteed, although one would certainly expect it, so that may just be an oversight.) – James Kanze Nov 08 '13 at 16:10
  • @JamesKanze if you are modifying `count` more than once within the initialization then does it matter that the initialization is a full expression? The modifications are happening before the end of the expression which is multiple modification within a sequence point. Unless you are saying each individual elements initialized is a full expression but I can't really see that language. – Shafik Yaghmour Nov 08 '13 at 16:19
  • The standard clearly says that the end of a full expression is a sequence point. (I quoted it elsewhere, §1.9/16.) And the complete aggregate initializer isn't an expression, at least not in the normative parts of the standard. – James Kanze Nov 08 '13 at 17:02