3

C99 introduced the concept of designated intializers for structs. So for example, given:

typedef struct {
    int c;
    char a;
    float b;
} X;

I could initialize like: X foo = {.a = '\1', .b = 2.0F, .c = 4}; and calling: printf("c = %d\na = %hhu\nb = %f", foo.c, foo.a, foo.b); would output:

c = 4
a = 1
b = 2.000000

As mentioned here this has the "surprising behavior" of assigning to c then a then b, independent of the order of my designated initializers.

This becomes a real issue if I have functions like this:

int i = 0;

int f() {
    return ++i;
}

int g() {
    i += 2;
    return i;
}

int h() {
    i += 4;
    return i;
}

And I want to initialize like this: X foo = {.a = (char)f(), .b = g(), .c = h()}; Now when I do: printf("c = %d\na = %hhu\nb = %f", foo.c, foo.a, foo.b); I get:

c = 4
a = 5
b = 7.000000

The problem being there was no warning that my initialization order was not respected. Is there a warning or something I can enable for this?

[Live Example]

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • Wow. +1. Didn't know that the order is unspecified! – Spikatrix Jan 05 '16 at 14:52
  • You are asking for a warning, but you haven't specified what compiler are you using. – 2501 Jan 05 '16 at 14:54
  • @CoolGuy Well, it's not unspecified precisely. It's just specified by the order of member declaration. But I agree, this was a shock to me as well when [Ben Voigt pointed it out](http://stackoverflow.com/questions/18731707/why-does-c11-not-support-designated-initializer-list-as-c99/29337570?noredirect=1#comment46864214_29337570). – Jonathan Mee Jan 05 '16 at 14:54
  • 4
    @CoolGuy *6.7.9, p23: 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)* – 2501 Jan 05 '16 at 14:55
  • @2501 Well I have a [link to gcc](http://ideone.com/WO3NEt) in the question. But I'd honestly like to see this warning mandated by the standard or something. – Jonathan Mee Jan 05 '16 at 14:56
  • 2
    @JonathanMee *(152) In particular, the evaluation order need not be the same as the order of subobject initialization.* It isn't specified by the order of member declaration! – 2501 Jan 05 '16 at 14:57
  • 3
    Very similar to the problem some have stated regarding the unspecified order in which arguments are processed in within a function argument list. – ryyker Jan 05 '16 at 14:59
  • @ryyker Interesting. I imagine that's a problem for both C and C++. – Jonathan Mee Jan 05 '16 at 15:00
  • 1
    I do not know that it is a problem. It is just something to be aware of. – ryyker Jan 05 '16 at 15:03
  • @2501 Visual Studio 2015 kept erroring when I tried the linked code. Turns out it compiles the file as a C file [based on the extension](http://stackoverflow.com/a/20821436/2642059) 0.0 Anyway it doesn't throw an warning either. – Jonathan Mee Jan 05 '16 at 15:09
  • 1
    I don't get how this is _"surprizing"_. We're not surprized when it comes to things like `sprintf("%d - %d - %d", ++i, i--, i++);`, simply because there are no sequence points to guarantee a given output. I don't really see how designated initialization should be any different – Elias Van Ootegem Jan 05 '16 at 15:32
  • @EliasVanOotegem This is different because I *did* specify an order and the functions were not called in the order that I specified. In your example the order of operations will always be what you specified in the call: increment, decrement, increment. In my question functions will not be called in the order I specify: `f()`, `g()`, `h()` but in the order the variables these functions assign to: `h()`, `f()`, `g()`. – Jonathan Mee Jan 05 '16 at 15:36
  • 1
    @JonathanMee: The order of operations is ***not*** guaranteed to be the order in which you pass arguments to the function. Some people (wrongly) assume that the arguments will be evaluated right to left, but according to the standard, the behaviour is undefined (there's a bunch of questions on here [about `printf` and UB](http://stackoverflow.com/a/12529703/1230836)) – Elias Van Ootegem Jan 05 '16 at 16:29
  • 1
    @EliasVanOotegem - interesting. When I started, I remember initially it was my assumption that order of evaluation was from _left to right_. `printf()` was my introduction to the discovery that the lack of a specified _order of evaluation_ was true for all C functions. – ryyker Jan 05 '16 at 18:11
  • 1
    @JonathanMee You specified and order, but there are many constructs in C where the order of evaluation is unspecified. See. e.g. http://stackoverflow.com/a/3458842/126769 – nos Jan 06 '16 at 00:09
  • @nos Apparently this is one of those constructs, huh? I noticed in your link that the `=` is not order specified. Does that mean `a = b = c` is bad code? I thought that operated right to left? – Jonathan Mee Jan 06 '16 at 12:32
  • 1
    @JonathanMee: Important to note that my comment was made in the context of C++11 and later initialization lists, which DO guarantee order. I'm not familiar with the C99 rules, but I thought aggregate initializers used a fixed order also in C. It's possible that designated initializers are an exception to that. – Ben Voigt Jan 06 '16 at 15:37
  • 1
    @EliasVanOotegem: We are not dealing with function arguments here, but braced aggregate initializers. – Ben Voigt Jan 06 '16 at 15:38
  • @BenVoigt How can C++ initialization lists guarantee order? They really just simplify to constructor arguments, and from my reading I believed that arguments were unsequenced. – Jonathan Mee Jan 06 '16 at 15:53
  • 1
    @JonathanMeearg when list initialization is involved, the C++ compiler basically sets up temporarily like the answer suggests, with each being a separate full expression (guaranteeing order). And then those are passed in, either as a single std::initializer_list or separately to a constructor with multiple parameters. But the side effects occur during list processing, not argument evaluation, so it doesn't matter that argument evaluation is unordered. – Ben Voigt Jan 06 '16 at 16:08
  • @BenVoigt Fascinating, I've tried to break an initialization list a couple times and haven't been able to succeed. Do you happen to have a reference for declaring that initialization lists do guarantee order? – Jonathan Mee Jan 06 '16 at 16:25
  • @BenVoigt: Yes, we're not dealing with function arguments, but like function arguments, there's no sequence point between the initializers, so as far as the standard goes, there's absolutely no requirement for the order to be respected, and side effects to be performed. That's why I mentioned function arguments: we don't expect the order of arguments to be predictable there, so why would desginated initializers be any different? – Elias Van Ootegem Jan 06 '16 at 16:37
  • @EliasVanOotegem I took your `printf` example and [put it in an initialization list](http://ideone.com/GgdVyo). Even when the `printf` executes in an unspecified behavior, the initialization list executes left to right. This is empirical evidence obviously, that's why [I'd like to see some documentation](http://stackoverflow.com/questions/34614308/is-there-a-way-to-get-warned-about-misbehaving-designated-initializers?noredirect=1#comment57020908_34614308) from Ben Voigt. – Jonathan Mee Jan 06 '16 at 16:56
  • @JonathanMee: C++ does have a couple of extra sequence points. Empirical evidence is all well and good (in **most** cases, left-to-right evaluation is what you'll observe). The thing with UB is that, even though 99% of compilers implement something the same way, it's non-standard, and you shouldn't expect the code you write to be as portable as pure standard C. If you're only targetting a specific compiler, you could arguably decide to take the risk, but because it's non standard, there's no guarantee that your code will compile come the next compiler update either. That's the danger of UB – Elias Van Ootegem Jan 06 '16 at 17:01
  • 1
    @JonathanMee: In C++, 8.5.4 says that "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." – Ben Voigt Jan 06 '16 at 18:15
  • (continued) " [ Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. — end note ]" – Ben Voigt Jan 06 '16 at 18:15
  • @EliasVanOotegem: This question grew out of my comment on [a C++ proposal to add designated initializers](http://htmlpreview.github.io/?https://raw.github.com/CTMacUser/multiarray-iso-proposal/master/designation-proposal.html) which certainly does specify the order. Also, C++ specifies the evaluation order for all braced initializers. It seems that Jonathan erred in tagging this question about C99 when it is formulated based on C++ rules. – Ben Voigt Jan 06 '16 at 18:21
  • @BenVoigt: I didn't see any references to C++ in the question (it mentions C99, which you say is a mistake on the OP's part). The live example is an ideone page where the OP specifically choose the language _C_, not C++. So I sort of assumed C was the language to focus on here. If not: You're right C++ does specify the order – Elias Van Ootegem Jan 06 '16 at 18:25
  • @EliasVanOotegem I chose C because C++ does not support designated initializers. But this question was spawned in response to me asking: [Why Not?](http://stackoverflow.com/a/29337570/2642059) – Jonathan Mee Jan 06 '16 at 19:29

2 Answers2

5

The best (read: reasonable) thing you can do in C, is to declare three temporary const variables before you initialize the struct. Their declaration order is the order of evaluation of their initializers.

Something like this:

const char a = f();
const float b = g();
const int c = h();

X foo = {.a = a, .b = b, .c = c};

In this case the order of function calls and the intent of the programmer is clear.

2501
  • 25,460
  • 4
  • 47
  • 87
  • 4
    I daresay the *best* thing to do is to avoid initialization expressions that have side effects. If that cannot be done then this approach constitutes a reasonable accommodation. – John Bollinger Jan 05 '16 at 15:02
  • This is clearly the right work around, but it'd be a pain to always engage in such a practice. Sure would be nice if compilers would warn me that I should do something like this. – Jonathan Mee Jan 05 '16 at 15:03
  • 1
    @JonathanMee I went through almost all of the gcc warning flags a while ago and I haven't found one for this. – 2501 Jan 05 '16 at 15:06
  • A problem with that approach is that there are certain contexts where a declaration is acceptable but a statement is not; a good solution may be to have a syntax which would allow a declaration to contain a statement within it. – supercat Jun 05 '18 at 21:20
4

...no warning that my initialization order was not respected.

A particular initialization order is an expectation based on something other then that stated in the standard. (as pointed out in the comments )

C99 section 6.7.9, p23: 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. [emphasis mine]

There is therefore no problem here except undefined (or unspecified) behavior. Very similar to other C behaviors such as the ambiguity with order of evaluation of function arguments.

EDIT
C99 has this to say about that:

from C99 §6.5.2.2p10:
Order of evaluation of function arguments is unspecified, The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.
[emphasis mine]

read more here

That you would prefer a warning (which you stated well, +1) is another matter. I am not sure how practical it would be though to provide a warning for -every- -undefined- -behavior- in the C/C++ languages.

It is interesting to note some of the stated assumptions/opinions in this discussion why the C++ standards do not include Designated Initializers. (Yet) ...

...C++ is more interested in putting the flexibility on the side of the designer of a type instead, so designers can make it easy to use a type correctly and difficult to use incorrectly.

Community
  • 1
  • 1
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • I've started trying to think about how we'd reach this same issue with function arguments. Those *should* all be called in the order that the programmer specifies though right? I mean if the programmer doesn't know what he's doing there could be unintended side effects, but that's really more on the programmer, right? – Jonathan Mee Jan 05 '16 at 15:24
  • @JonathanMee: when you say, about function arguments, _"Those should all be called in the order that the programmer specifies though right?"_ You're making a dangerous assumption. The comma separating arguments is *not* a sequence point, so in the same way as an expression like `f1()+f2()` can call `f2` first, or `f1`, expressions that make up function arguments can be evaluated in any order the compiler chooses. if you want to actually _choose_ the order, inline assembly is probably the only way to go. In C, the order of argument evaluation is unspecified, thus its behaviour undefined – Elias Van Ootegem Jan 05 '16 at 16:36
  • @EliasVanOotegem Thanks to [your comment](http://stackoverflow.com/questions/34614308/is-there-a-way-to-get-warned-about-misbehaving-designated-initializers?noredirect=1#comment56978498_34614308) I see that `printf` may be evaluated in any order, but I understood that to be because `printf` arguments are part of a [`va_list`](http://en.cppreference.com/w/c/variadic/va_list). Are you saying that the arguments to *any* function are executed in an arbitrary order? – Jonathan Mee Jan 05 '16 at 17:04
  • 1
    @JonathanMee: Yes, that's exactly what I'm saying. even calling `int your_function(int a, int b) { return a - b; }` does not guarantee that the expressions you pass will be evaluated left to right. There's no sequence point (a point at which all side effects of previous evaluations are performed). Take [this example](https://eval.in/497763). The nested call is a sequence point, so the outer call passes `i+1 (13)`, and the return value of the inner call (undefined, in this case -1 because `i++, i` evaluates to `12, 13` apparently), but there's no guarantee that this will always be the case – Elias Van Ootegem Jan 05 '16 at 17:24
  • @EliasVanOotegem Something's fishy here. You call `printf("%d - %d - %d\n", i, your_function(++i, ++i), i);` and get the output: "12 - 0 - 12" Whatever on the "12"'s but there's no plausible way the middle should print "0". Either `a` should be 11 and `b` should be 12 or `b` should be 11 and `a` should be 12. How would they both pass as the same number? – Jonathan Mee Jan 05 '16 at 17:42
  • 1
    @JonathanMee: `++i` is a pre-increment, so in this case `i` is incremented twice before the `your_function` call is made (so the call is actually `your_function(12, 12)`, returning `12 - 12` -> 0. Again this is because there's no sequence point in between the 2 arguments: C is allowed to perform the 2 increments _before_ calling the function, and that's why both `a` and `b` are 12. If you move one of the increments to outside the call, it's possible that the results differ, but they could be the same – Elias Van Ootegem Jan 05 '16 at 17:45
  • @EliasVanOotegem Fascinating. I've actually never heard the term "sequence point". I assume that's defined as the point at which the compiled code must conform to the C code? – Jonathan Mee Jan 05 '16 at 18:29
  • 1
    @JonathanMee: In a way: The standard describes them as such: _"At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place. (§1.9/7)"_ They're predefined situations where the compiled code must have executed every single expression that was in the C code, there's [a wiki page](https://en.wikipedia.org/wiki/Sequence_point) that should clear everything up – Elias Van Ootegem Jan 05 '16 at 18:33
  • "sequence points" are obsolete, at least in C++. Since C++11, things are defined in terms of "happens before" relationships. On the other hand, you'll still hear people talking about sequence points because they were such a useful abstraction. – Ben Voigt Jan 06 '16 at 15:35
  • @EliasVanOotegem I asked a question about your `printf` example [here](http://stackoverflow.com/q/34637712/2642059). Cause I feel like [this](https://en.wikipedia.org/wiki/Sequence_point#Sequence_points_in_C_and_C.2B.2B) says that it should have expected sequencing. (Even though like your example, I'm not seeing that.) – Jonathan Mee Jan 06 '16 at 16:19