23

I noticed just now that the following code can be compiled with clang/gcc/clang++/g++, using c99, c11, c++11 standards.

int main(void) {
    int i = i;
}

and even with -Wall -Wextra, none of the compilers even reports warnings.

By modifying the code to int i = i + 1; and with -Wall, they may report:

why.c:2:13: warning: variable 'i' is uninitialized when used within its own initialization [-Wuninitialized]
    int i = i + 1;
        ~   ^
1 warning generated.

My questions:

  • Why is this even allowed by compilers?
  • What does the C/C++ standards say about this? Specifically, what's the behavior of this? UB or implementation dependent?
Lundin
  • 195,001
  • 40
  • 254
  • 396
Hongxu Chen
  • 5,240
  • 2
  • 45
  • 85
  • 2
    There is nothing "even" in `-Wall -Wextra`. That's about the bare minimum in warnings. See [this answer](https://stackoverflow.com/questions/2408038) of mine to an older question about `-Wall`... – DevSolar Jan 15 '19 at 14:09
  • 1
    [`-Wall` is enough for me to get a warning for gcc](https://godbolt.org/z/X0qFUA) – Kevin Jan 15 '19 at 14:12
  • @Kevin no dice with gcc 7. gcc 8 seems to detect the issue – Jean-François Fabre Jan 15 '19 at 14:17
  • @Jean-FrançoisFabre Maybe godbolt isn't working right, but I can set it back to gcc 4.4 and it still gives me the warning. – Kevin Jan 15 '19 at 14:20
  • okaY. Anyway it's a compiler limitation or bug if it doesn't detect that, because it can cause issues later. – Jean-François Fabre Jan 15 '19 at 14:21
  • @Kevin watch it, you're using a c++ compiler!! – Sourav Ghosh Jan 15 '19 at 14:29
  • @SouravGhosh I hadn't noticed that, but this question has the C++ tag as well. Oddly enough with C I only get it with both `-Wall` and `-Winit-self` but the warning is from `-Wuninitialized` – Kevin Jan 15 '19 at 14:37
  • 2
    Duplicate: [Why does the compiler allow initializing a variable with itself?](https://stackoverflow.com/questions/16110546/why-does-the-compiler-allow-initializing-a-variable-with-itself). However, the answers there are not very good. So lets leave this open for now and see if something better comes up. Then we can close the linked post instead. – Lundin Jan 15 '19 at 14:52
  • 2
    I improved the title and will mop up some old, bad duplicates, since the answers posted here so far are already better than those posted for the dupe questions. – Lundin Jan 15 '19 at 15:48
  • 1
    The C++ part is a duplicate of https://stackoverflow.com/q/14935722/5376789 – xskxzr Jan 15 '19 at 16:07

3 Answers3

17

Because i is uninitialized when use to initialize itself, it has an indeterminate value at that time. An indeterminate value can be either an unspecified value or a trap representation.

If your implementation supports padding bits in integer types and if the indeterminate value in question happens to be a trap representation, then using it results in undefined behavior.

If your implementation does not have padding in integers, then the value is simply unspecified and there is no undefined behavior.

EDIT:

To elaborate further, the behavior can still be undefined if i never has its address taken at some point. This is detailed in section 6.3.2.1p2 of the C11 standard:

If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

So if you never take the address of i, then you have undefined behavior. Otherwise, the statements above apply.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • It is perhaps relevant to include that the scope of identifier `i` begins at the end of its declarator, of which the initializer is not part. Thus `i` is in scope in its own initializer, even though it is not useful to initialize it with itself. – John Bollinger Jan 15 '19 at 14:26
  • Also, there's a bit of a wiggly issue around [C11 6.3.2.1/2](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p2), though I don't think that makes your analysis incorrect. – John Bollinger Jan 15 '19 at 14:32
  • Can you elaborate this, better with reference to the standard? – Hongxu Chen Jan 15 '19 at 14:36
  • @JohnBollinger This answer is essentially correct. The complete story including the 6.3.2.1 special case can be found here: [(Why) is using an uninitialized variable undefined behavior?](https://stackoverflow.com/a/40674888/584518) – Lundin Jan 15 '19 at 15:43
  • Yes, @Lundin, I didn't say otherwise. My point is that the OP's code almost, but does not quite, fall into the case covered by 6.3.2.1/2, to which the question of trap representations is irrelevant. – John Bollinger Jan 15 '19 at 15:47
  • 1
    @JohnBollinger Added further detail on whether `i` had its address taken. – dbush Jan 15 '19 at 15:52
  • That's useful, thanks, but what I was more looking at is that 6.3.2.1/2 fails to apply here because `i` has an initializer -- the very initializer whose behavior is in question. If instead of an initializer there were an assignment `i = i` then the behavior would be unconditionally undefined. Already upvoted, though. – John Bollinger Jan 15 '19 at 15:55
  • @JohnBollinger That brings up an interesting point. `i` was "declared with an initializer" and is therefor not uninitialized as per 6.3.2.1p2, but at the same time it *is* uninitialized at the time it is used. So which one applies? – dbush Jan 15 '19 at 18:15
  • Yes, that's what I was getting at. But I'm prepared to accept a literal, albeit counterintuitive, reading of the standard. That is, because `i` is declared with an initializer, it is not "uninitialized", as that term is used in context, even in the initializer. In any case, that doesn't change my advice to any and everyone about code like this: *don't do that!* – John Bollinger Jan 15 '19 at 18:36
  • I think you need to read the standard as: initialization follows the same rules as per simple assignment. During simple assignment, the operands are unsequenced, which probably also makes this UB. Although the evaluation of the right operand has to be done before the point where it is stored. And at that point the variable is uninitialized. I really can't come up with a scenario where this wouldn't be one form of UB or another. – Lundin Jan 16 '19 at 07:43
  • _An indeterminate value can be either an unspecified value or a trap representation._ Even though it is defined this way in the Standard, the C Standard Committee seem to treat indeterminate value as a distinct value different from any other value. – Language Lawyer Jan 16 '19 at 15:52
  • Note: the question is dual-tagged; this answer only applies to C – M.M Aug 02 '19 at 02:36
11

This is a warning, it's not related to the standard.

Warnings are heuristic with "optimistic" approach. The warning is issued only when the compiler is sure that it's going to be a problem. In cases like this you have better luck with clang or newest versions of gcc as stated in comments (see another related question of mine: why am I not getting an "used uninitialized" warning from gcc in this trivial example?).

anyway, in the first case:

int i = i;

does nothing, since i==i already. It is possible that the assignment is completely optimized out as it's useless. With compilers which don't "see" self-initialization as a problem you can do this without a warning:

int i = i;
printf("%d\n",i);

Whereas this triggers a warning all right:

int i;
printf("%d\n",i);

Still, it's bad enough not to be warned about this, since from now on i is seen as initialized.

In the second case:

int i = i + 1;

A computation between an uninitialized value and 1 must be performed. Undefined behaviour happens there.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • 1
    Good answer; however, I am not sure about the "warnings issued, when the compiler is sure that there is going to be a problem". There are many warnings, where this isn't the case, this is highly dependent on the warning types enabled – Ctx Jan 15 '19 at 14:12
  • 2
    The risk with `int i = i + 1` is that UB is UB, period. Also, signed overflow. Also, much scratching of heads when another coder has to make sense of that code at some later point. – DevSolar Jan 15 '19 at 14:13
  • true! I forgot the _sign_. Edited out to make it simple – Jean-François Fabre Jan 15 '19 at 14:14
  • As @Ctx says. For example, I often find myself building third-party code that causes tons of warnings about use of *possibly* uninitialized variables. Control-flow analysis in those cases normally shows that the programmer included appropriate logic to ensure that the variable is assigned a value before its value is used. But the compiler is explicitly *un*sure whether there is a problem. – John Bollinger Jan 15 '19 at 14:15
  • `-Wparentheses` would be another example. – Osiris Jan 15 '19 at 14:23
  • The compiler doesn't _have_ to give a diagnostic here, although any half-decent compiler will. With `-Wall -Wextra`, icc gives a warning for `i=i;`, but gcc and clang don't. Not unless we do something like `int i=i+1;`. Apparently there's something called `-Winit-self` that has to be used explicitly. – Lundin Jan 15 '19 at 16:02
  • @Lundin Depends on the compiler version & platform apparently. gcc 7.3 windows remains silent even with `-Winit-self`. – Jean-François Fabre Jan 15 '19 at 17:37
  • `int i = i;` is undefined behaviour; one possible manifestation of which is "does nothing". Your answer seems to imply that it's harmless – M.M Aug 02 '19 at 02:35
4

I believe you are okay with getting the warning in case of

int i = i + 1; 

as expected, however, you expect the warning to be displayed even in case of

int i = i;

also.

Why is this even allowed by compilers?

There is nothing inherently wrong with the statement. See the related discussions:

for more insight.

What does the C/C++ standards say about this? Specifically, what's the behavior of this? UB or implementation dependent?

This is undefined behavior, as the type int can have trap representation and you never have taken the address of the variable in discussion. So, technically, you'll face UB as soon as you try to use the (indeterminate) value stored in variable i.

You should turn on your compiler warnings. In gcc,

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261