17

Possible Duplicate:
array initialization, is referencing a previous element ok?

I wonder if its safe to do such initialization in c/c++ standard:

int a = 5;
int tab[] = { a , tab[0] + 1 , tab[1] };

It successfully compiles and executes with gcc 4.5 and clang 2.9, but will it always be true?


Printing this table gives 5 6 6. Its initialized in global scope.


Generally its interesting in both, c and c++, but i want to use it in c++:)

cigien
  • 57,834
  • 11
  • 73
  • 112
qba
  • 1,291
  • 3
  • 15
  • 22
  • 6
    @一二三: not really no. – Mat Sep 11 '11 at 13:32
  • 1
    why not just write `int tab[] = {a, a+1, a+1}`? I don't get the point. – Andreas Grapentin Sep 11 '11 at 13:34
  • 1
    @Tomalak Whereas I know that there is no C/C++, it would still be nice to know what both standards say about this. – Christian Rau Sep 11 '11 at 13:35
  • @Andreas The question is surely not how to best accomplish this or how you can do it alternatively, but if this is UB or not. And this is IMHO a very interesting question which I myself would like to be answered. – Christian Rau Sep 11 '11 at 13:37
  • @Andreas: The point is to know the rules for referring to previous element initialisers later on in a list-initialiser. Seems pretty clear. – Lightness Races in Orbit Sep 11 '11 at 13:37
  • using gcc with -pedantic, the warning "initializer element is not computable at load time" is returned, and the array is not initialized correctly. however, using g++, no warning is returned and the initialization is performed correctly. just for the record :) I don't know what the standard definition says though. In other words - works in C++, does not work in C. I'd generally consider it unsafe though. – Andreas Grapentin Sep 11 '11 at 13:49
  • have you tried to run this with different arguments of gcc like -std=c99 and -std=c++03 to verify that gcc outputs the same values using different parser variants? – Alexander Oh Sep 11 '11 at 15:03
  • @Alex - interested in the results? :) see my answer below – Andreas Grapentin Sep 11 '11 at 17:16

5 Answers5

7

C++03/C++11 answer


No, it won't.

On the right-hand side of the =, tab exists1 but — if it has automatic storage duration — it has not yet been initialised so your use of tab[0] and tab[1] is using an uninitialised variable.

If tab is at namespace scope (and thus has static storage duration and has been zero-initialized), then this is "safe" but your use of tab[0] there is not going to give you 5.

It's difficult to provide standard references for this, other than to say that there is nothing in 8.5 "Initializers" that explicitly makes this possible, and rules elsewhere fill in the rest.


1 [n3290: 3.3.2/1]: The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any) [..]

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • With g++ and clang result is `5 6 6`, so what i expected. – qba Sep 11 '11 at 14:00
  • 2
    @qba: Read my answer again. You can't guarantee this result. – Lightness Races in Orbit Sep 11 '11 at 14:15
  • Is it really "safe"? Doesn't the dynamic initialization mean that the object is written to, so doesn't reading it to calculate the value to be stored in a different array element cause UB because neither operation is sequenced before the other? – CB Bailey Sep 11 '11 at 17:55
  • @Charles: It's safe to read from a static before you initialised it, because it was already zero-initialised. Unless you're referring to _that_ mechanism...? – Lightness Races in Orbit Sep 11 '11 at 17:57
  • `[n3290: 3.6.2/2]: Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place. [..]` – Lightness Races in Orbit Sep 11 '11 at 17:58
  • Errr, no, what I meant is that in the declaration statement `tab[0]` is being written to with a new value, then it is being read to determine the value to be stored in `tab[1]`. I'm not sure if there's a "sequence point" violation. I think there isn't a problem in _list initialization_ because the sequencing is explicitly specified, but I wasn't sure about _aggregate initialization_. – CB Bailey Sep 11 '11 at 18:31
  • @Charles: You're assuming semantics that I just wrote a whole answer stating don't exist. It is not specified that the first element is written to before the second item in the initialiser is evaluated. And array initialisation with a braced initialiser uses list-initialization. – Lightness Races in Orbit Sep 11 '11 at 18:38
  • I understand that what you are saying in the comment is that it is UB (which I am inclined to believe) because the standard does not establish sequence points between the initialization of different elements. On the other hand, your answer states that for objects with static duration it would be safe, which contradicts this, that is, the same source of UB is still present in the case of static duration... is it not? – David Rodríguez - dribeas Sep 11 '11 at 18:42
  • @David: Hmm, good question. And I get what Charles is saying now. I'm not really sure. I suppose it would have to be UB... – Lightness Races in Orbit Sep 11 '11 at 18:46
  • @Charles: I'm not sure about 8.5.4/4. Does it apply to aggregate initialization? Value computation and side-effects are ordered before the next initializer clause is evaluated but what about the actual initialization of the target with the value computed? – CB Bailey Sep 11 '11 at 18:55
  • Talking to yourself now. – Lightness Races in Orbit Sep 11 '11 at 18:56
  • @AnyoneWhosListening: I was countering, or at least challenging the third sentence in my second comment. – CB Bailey Sep 11 '11 at 18:58
  • @Charles: *If* 8.5.4/4 does apply to aggregate initialization I don't think it would guarantee the code in the question, as that same clause is used for initialization of classes with a constructor that takes a `std::initializer_list` in which case, due to the call to the constructor, the construction of the argument must complete before the constructor is called. – David Rodríguez - dribeas Sep 11 '11 at 19:17
5
int a =5;
int tab[] = { a , tab[0] + 1 , tab[1] };

If these variables are declared at namespace scope, then they're okay, as at namespace scope variables are zero-initialized (because of static initialization - read this for detail).

But if they're declared at function scope, then second line invokes undefined behaviour, since the local variables are not statically initialized, that means, tab[0] and tab[1] are uninitialized, which you use to initialize the array. Reading uninitialized variables invokes undefined behavior.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
4

In the C99 standard, it seems that the order of initialization of the members is guaranteed:

§6.7.8/17: Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union. In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator. Initialization then continues forward in order, beginning with the next subobject after that described by the designator.

But as @Tomalak mentions in the comment, that does not provide a full guarantee for the operation, as the compiler would first evaluate all of the arguments and then apply the results in the previous order. That is, the previous quote does not impose an order between the initialization of tab[0] and the evaluation of the expression tab[0]+1 that is used to initialize tab[1] (it only imposes an ordering between the initialization of tab[0] and tab[1])

As of the C++ standard, neither in the current standard nor the FDIS of the upcoming C++0x standard seem to have an specific clause defining the order in which the initialization is performed. The only mention of ordering comes from

§8.5.1/2 When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace-enclosed, comma-separated list of initializer-clauses for the members of the aggregate, written in increasing subscript or member order.

But that only relates to the order by which the entries in the initializer are written, not how it is actually evaluated.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
4

so, now I've run a couple of tests regarding your problem.

All compilations have been performed with your example code above, using the following scheme:

$(GCC) -o a.out test.c -Wall -Wextra -pedantic -std=$(STD)

This yielded the following results:

for GCC = gcc, the standards -std=c89; -std=iso9899:1990; -std=iso9899:199409; -std=gnu89 resulted in a warning showing up: initializer element is not computable at load time and undefined behaviour at runtime, meaning that the second and third value of the array were random garbage.

the standards -std=c99; std=iso9899:1999; -std=gnu99 did not produce this warning, but also showed undefined behaviour at runtime.

for GCC = g++, the standards -std=c++98; -std=gnu++98; -std=c++0x produced no warning, and the code worked as you'd expected it to, resulting in an array containing the values {5, 6, 6}.

However, as most of the people advised, it might be unwise to use this, since your code might behave differently on other compilers, or maybe even other versions of the same compiler, which is generally a bad thing :)

hope that helped.

Andreas Grapentin
  • 5,499
  • 4
  • 39
  • 57
  • How did you manage to detect undefined behaviour, pray tell? – Lightness Races in Orbit Sep 11 '11 at 17:59
  • BTW it's not just about "other compilers, or maybe even other versions of the same compiler", but even different runs with the _same_ compiler; or perhaps it only _appeared_ to work in your case but in fact didn't at all and instead created a finger-sized hole in the universe..? – Lightness Races in Orbit Sep 11 '11 at 18:00
  • @Tomalak I printf()'d the contents of the array. that's a very reliable way to detect UD imo. And I was only stating experimental results, without any claim of completeness. If you find an example of g++ creating an executable where the declaration of the question above does *not* produce the intended results, let me know :) - My guess is, that the g++ detects initializations of this kind and handles them appropriately. But, as I said, I wouldn't rely on it, and would most certainly never use it. – Andreas Grapentin Sep 11 '11 at 18:31
  • It is mathematically impossible, in many cases, to "detect" UB. Certainly, `printf`-ing something is **never** a UB detection tool. – Lightness Races in Orbit Sep 11 '11 at 18:37
  • well, of course exact detection is generally not possible. *But* the case we have here is pretty simple, in a way that allows to identify undefined behaviour *if* something went wrong and all other possible sources for "something wrong" have been eliminated. The probability that it's not UB when something went wrong is insignificant, and can thus be eliminated almost completely by running every test at least twice. – Andreas Grapentin Sep 11 '11 at 18:43
  • thanks for the quick analysis even though this says nothing about the standard conformance of the code. If different compiler settings yield a different output I think it's feasible to avoid using this kind of initialization. However I think in haskell this is rather normal to define infinite arrays (due to it's lazy evaluation). +1 – Alexander Oh Sep 12 '11 at 10:48
0

Yes - It will probably work as you expect it to be.
No - (you didn't ask but) Don't use it, it make no logic and it's a bad practice.

Roee Gavirel
  • 18,955
  • 12
  • 67
  • 94