1

I have written a program in C, where I accidentally put an uninitialized variable int. This int is written inside a function, but somehow when I print it after incrementing, it works just fine. Why is this? Is it not supposed to be undefined behaviour, printing literally garbage?

int n;
int counter;

for (counter = 0; limit > counter; counter++) {
      ++n;
      printf("%d\n", n);
} 
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 9
    Undefined behavior can be literally anything, from the program crashing, to it printing something sane-looking. In your case you ended up with UB that allows the program to keep running (and perhaps by chance the original value of the unitinitialized variable `n` was zero or something) – nanofarad Feb 17 '19 at 19:40
  • Welcome to StackOverflow! Can you add information about your compiler? – Alex Yu Feb 17 '19 at 19:43
  • 2
    Why should it print garbage? That would be defined, but the behaviour is undefined. I can't yet find a good duplicate, but [this question](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) may be of general interest – Weather Vane Feb 17 '19 at 19:45
  • You say the code "works just fine" but what do you mean? If the loop iterates at all, it will keep iterating at least until one or both of `n` and `counter` overflows, since they are both incremented in the loop. – Weather Vane Feb 17 '19 at 19:56
  • @WeatherVane I made a typing mistake translating the variable names from Danish. I know the value of the limit, and when the loop is finished, n is printed with the same value as the loop. – BendixDeveloper Feb 17 '19 at 20:07
  • @AlexYu I am using gcc – BendixDeveloper Feb 17 '19 at 20:08
  • Thank you all for the response! – BendixDeveloper Feb 17 '19 at 20:08
  • 3
    That is one reason why we like a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) that shows the question — to avoid typos and false conclusions. – Weather Vane Feb 17 '19 at 20:10
  • You've not shown what `limit` is, which makes it impossible for us to know what happens with the loop. If `limit` is negative or zero, the loop body is never executed. – Jonathan Leffler Feb 17 '19 at 21:55
  • `0` can be garbage. There is no single value to represent "garbage" (did you maybe expect `13377331`?) – pmg Feb 18 '19 at 21:11

3 Answers3

2

It’s undefined behaviour to access the value of an uninitialized variable. Most compilers will at least warn about this, and MSVC 2017 will by default treat this as an error.

You have no way of knowing what it contains initially, and if it happens to contain the number of loop iterations that you intended right off the bat, then using it as a loop guard without first assigning it a value will appear to work for that running instance of your program.

Govind Parmar
  • 20,656
  • 7
  • 53
  • 85
  • An uninitialized object is not defined to get “whatever is in memory at its address.” Optimization might result in an uninitialized object behaving as if it got whatever is in a register the compiler happened to use for it (having optimized away the pointless load). Or the optimizer might optimize away the entire code path containing the reference to the uninitialized object. (`int x; if (something) { printf("Hello, world.\n"); return x; } else { printf("Good-bye.\n"); }` might print “Good-bye” even if `something` is true.) Or the program might trap. – Eric Postpischil Feb 17 '19 at 19:50
  • It is not undefined behavior to access an uninitialized object **since** you have no way of knowing it it contains initially—the fact that you do not know what it contains is not the cause of undefined behavior. Many accesses to uninitialized objects have behavior not defined by the C standard, but that is not always true. The rules are actually tricky, so blanket statements like that are not correct. – Eric Postpischil Feb 17 '19 at 19:53
  • A lesson well worth the 2pt cost of admission. I once though @EricPostpischil was wrong -- but I was mistaken... – David C. Rankin Feb 17 '19 at 19:54
  • 1
    See [(Why) is using an uninitialized variable undefined behavior?](https://stackoverflow.com/q/11962457/298225). – Eric Postpischil Feb 17 '19 at 19:56
  • @Eric, on first read, you seem to be stating it's not UB. I believe it *is* UB to access an uninitialised variable (well, assuming it has automatic stroage duration as the question seems to show). `C11 6.7.9 /10` and `J.2` support this, as per my answer. However, re-reading your comment, I think you meant it's not "UB caused by the reason given in this answer", but it's actually "UB regardless of that". Just thought I'd clarify, or maybe I've obscured :-) – paxdiablo Feb 18 '19 at 01:02
  • @paxdiablo: There is no general rule in the C standard that the behavior of accessing an uninitialized object of automatic storage duration is not defined by the standard. If an object is automatic and its address has not been taken, the behavior is undefined. If its address has been taken, other rules come into play. For an `unsigned char`, the behavior is not undefined, but it has an unspecified value. For other objects, rules about padding and trap representations come into play, and the behavior is not universally undefined by the standard but interacts with the implementation. – Eric Postpischil Feb 18 '19 at 01:11
  • @Eric, I *think* we may be seeing UB from different viewpoints. The standard seems to clearly state that it's UB to access uninitialised variables. That doesn't necessarily mean it won't *work* since that depends on the implementation (which seems to be where you're coming from). But the fact that a given implementation may work in no way stops the action from being UB according to the standard. Or have I misread what you're trying to say? – paxdiablo Feb 18 '19 at 02:05
  • @paxdiablo: The standard does not say the behavior of accessing an uninitialized object is not defined by the standard. – Eric Postpischil Feb 18 '19 at 02:35
  • @Eric, how do you then explain J.2, which specifically states that accessing it is UB? – paxdiablo Feb 18 '19 at 05:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188571/discussion-between-paxdiablo-and-eric-postpischil). – paxdiablo Feb 18 '19 at 06:36
  • @paxdiablo: Annex J is not normative. Read [Jens Gustedt’s answer](https://stackoverflow.com/a/11965368/298225) at the link I provided above. – Eric Postpischil Feb 18 '19 at 12:35
2

Well, if it was guaranteed to fail, it wouldn't be undefined behaviour(a), it would be defined to fail. UB means, literally, anything can happen, including the possibility that it works as you expected.

That doesn't mean it can be relied upon. You should still avoid UB simply because it may misbehave in often weird ways. In fact, that code of yours is well within its rights to print a sane value whilst also silently modifying random Excel spreadsheets on your desk :-)


(a) See C11 6.7.9 Initialization /10:

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.

And C11 J.2 Undefined behaviour:

The value of an object with automatic storage duration is used while it is indeterminate.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
-1

When the Standard was written, most forms of "undefined behavior" recognized the fact that implementations intended for different platforms and purposes would behave in different ways--some useful and predictable, and others not, and recognized that "the marketplace" [as the C Rationale document described it] would be better placed than the Committee to judge which implementations should be expected to behave in what fashions.

Historically, on most platforms, it would cost nothing for compilers to behave as though automatic objects were initialized in some arbitrary fashion that would be too consistent to be useful as any kind of random number generator, but not reliably predictable enough to be used for any other purpose except in cases where any possible value would be as good as any other [e.g. because it's often easier to copy an object without regard for whether it holds a useful value may be cheaper than to avoid copying it if it doesn't hold one]. On some platforms, however, the only way for a compiler to ensure such behavior would be for it to explicitly initialize such objects itself. The authors of the Standard didn't want to require that compilers for such platforms initialize objects with dummy values that would likely get overwritten by the programmer, and opted instead to require that programmers whose code had to be compatible with such platforms would have to ensure that nothing got used without initialization.

Since then, however, things have evolved into a worst-of-both-worlds direction. The authors of the Standard made no effort to mandate that implementations should guarantee that that automatic objects would behave as though initialized with arbitrary values when doing so would offer some benefit at generally-zero cost, because they saw no reason to expect implementations to do anything else in such situations. Today, however, some compilers will use the fact that an action as Undefined Behavior as justification for assuming that no program will ever receive inputs that would result in that action. Because such assumptions aren't usually very useful, they usually have no effect on program behavior. The notion that all UB results in nonsense behavior, which you seem to be alluding to, stems from the fact that an implementation that uses UB to infer that things won't happen, when they actually do, is prone to generate completely nonsensical code in such situations. For example, an aggressive optimizer might see something like:

void test(int x)
{
  int y,z;
  if (x == 23)
    y=z;
  printf("%d\n",x);
}

and infer that it would be "impossible" for the function to be invoked with any value of x other than 23, and therefore the printf should be replaced with puts("23");. I don't think any compilers are quite that aggressive yet, but it seems fashionable to view generation of code that would be capable of outputting other values of x as a "missed optimization".

supercat
  • 77,689
  • 9
  • 166
  • 211