One way of answering this is that when you read that "uninitialized variables start out containing random values", in this context, those values 0, 16, 15, and -1 that you saw are "random".
Now, it's true, they're certainly not random in the way that, say, the roll of a dice is random. They seem to stay the same, then change for no seemingly sensible reason, like when you change some other part of your program.
But you obviously can't predict them. So you obviously can't use them, or depend on them in any way. They might as well be truly random.
Why do they have the specific values that they do? It's almost impossible to say. Whatever the reasons are, they tend not to be very interesting. Whatever the reasons are, they're usually not important to know, since in any sane program you're never going to be depending on those uninitialized values anyway.
It's kind of like asking, "I sawed three legs off my kitchen table. Everything fell to the floor and broke. Except one plate didn't break. Why didn't it break? Also there was an apple, and it rolled into the living room. I expected it would roll down the basement stairs. Why did it roll into the living room?" Most of the time, the answer is simply: "Don't saw legs off your table in the first place!"
The other part of your question had to do with the fact that your uninitialized variable y
seemed to start out containing the value 16, but when you added the statement y--
, the value went to -1. What's up with that?
And the answer is that we're talking about uninitialized variables and unpredictable behavior. When you fail to initialize a variable, meaning that your program has unpredictable behavior, what does not happen is that some oracle somewhere picks an initial value for your variable, and then arranges for everything else about your program to be predictable. No, what it means for your program to be unpredictable is that you can't predict it! If the unpredictable value seemed to be 16 yesterday, you cannot predict that subtracting 1 from it will give you 15 tomorrow.
(To continue my silly analogy, if you were to saw the legs off your kitchen table again tomorrow, you would not expect that you'd necessarily end up with exactly one unbroken plate again. And if you sawed the legs off in a different order, hoping for the table to fall at a different angle and direct the apple down the basement stairs like you expected, you probably wouldn't be too disappointed if it didn't work.)
If you really want to know what's going on, part of the answer is that local variables are typically stored on the stack, and they start out containing whatever "random" bit pattern was left in that location on the stack by whatever function last used that piece of the stack for its own variables. So anything that moves things around on the stack, that causes your uninitialized variable to occupy a different spot in the stack frame, or that causes the previous function to leave different garbage on the stack, will cause your uninitialized variable to start out holding a different "random" value. See this other answer for some more discussion on what happens when you try to use uninitialized stack-based variables.
See also xkcd 221.