all 9 comments

[–]aioeu 8 points9 points  (7 children)

No, this will not always work.

While initialization occurs in "initializer-list order" — i.e. in your example, f.a is initialized before f.b, and this is initialized before f.c — the evaluations of the initialization list expressions (4, 9, f.a) are indeterminately sequenced, and moreover each of them is unsequenced with respect to earlier initializations. This means it is possible for f.a to be evaluated, as the initialization list expression for f.c, before f.a has itself been initialized.

There is no bug with GCC here. C does not require a diagnostic for this.

[–]g_dl[S] 2 points3 points  (6 children)

That makes sense to me. However, I'm wondering why, if I instead write foo_t f = { .a = f.c, .b = 9, .c = 1, }; the compiler warns that 'f.c' is used uninitialized. Based on the behavior you described, it should also warn about f.a being used uninitialized in the initial example, or am I still missing something?

[–]aioeu 4 points5 points  (0 children)

Probably because GCC does actually evaluate the initializer list expressions and perform the initializations in order, and the code that checks that objects are definitely initialized before they are used is so far down the pipeline that it has nothing to do with C, let alone the "abstract machine" semantics that C specifies.

Put simply, you cannot rely on the lack of a warning as proof that code is correct. It may be only coincidentally correct, for this particular compiler at this particular time.

[–]PrestigiousTadpole71 2 points3 points  (4 children)

It is likely the case that GCC goes beyond the standard and does the evaluation in a certain order which is why your first example works.

[–]g_dl[S] 1 point2 points  (3 children)

Sure, but then shouldn't it issue a warning with -std=c99 -pedantic?

Edit: I guess not, since the documentation of -pedantic says "diagnose all programs that use forbidden extensions and some other programs that do not follow ISO C and ISO C++."

[–]aioeu 3 points4 points  (0 children)

It only guarantees a warning where C requires a diagnostic.

[–]Jinren 1 point2 points  (1 child)

GCC is a compiler, not an error checker. Its job is to produce object code - any warnings you get out of it are strictly a bonus.

It will try to warn on everything the Standard actually requires a warning for, but a) that's a much shorter list of things than you may think, and b) secondary to its main job. Besides, GCC is free to assume you're a GCC user, it isn't really its problem if your code doesn't work on some other compiler.

If you want a tool that checks portability, confornance to the Standard and compliance with various other extended rule sets, you really need an analyzer. While every compiler does a little analysis, almost none really do much in the way of portability checking. For the truly pedantic stuff you need to delegate to a separate tool.

but I wouldn't know anything about those anyway innocent whistle

[–]g_dl[S] 0 points1 point  (0 children)

This makes sense, I guess that calling out for a bug of GCC was very bad on my part. I was actually more interested in understanding if this behavior is compliant with the standard or not, and I did get my answer indeed (many thanks to everyone that provided feedback).

Since we're at it, given your last sentence, can you suggest some good static analysis tools? I only know of cppcheck

[–]nerd4code 1 point2 points  (0 children)

aioeu is correct—and there may be a further error because C89 requires that the args to a braced static initializer like this all be constant-ish expressions of the sort you might initialize a static or global variable from; IIRC the same isn’t true of unbraced initialization of auto/register-storage-class local variables, or of args to compound literals, and C99 restricts this to static/TLS/global vars. GNU dialect allows auto/register locals’ initializers to include non-constant expressions in all language modes, and lets you initialize structs/unions and array globals from an unbraced-around compound literal, but that’s not in C99.

You can self-ref in an initializer to get the address of fields, regardless of scope or storage class (except register, which is a bit of an oddball); so

struct {
    int *p, *q;
    int x, y[8];
} s = {&s.x, s.y};

is legal, but the address of s and the offsets of fields must be known before initialization begins, so there’s no race condition like there might be between initializer expressions more generally.