The reason you see the results you do is:
When you pass an int
argument but use a printf specifier for double
(remember that a float
is converted to a double
in this situation), then most C implementations pass the int
argument according to their usual rules for passing a int
argument to a variadic function (a function that accepts diverse argument types), but the printf
routine interprets the machine state as if it were passed a double
argument, as described below. (This is not necessarily what always happens; once you leave the behavior defined by the C standard, a C implementation may do other things. In particular, there can be complex interactions with the optimizer that cause surprising results. However, this is what happens most commonly. You cannot rely on it.)
Each computing platform has some rules for how arguments are passed. One platform might specify that all arguments are pushed onto the stack, from right to left, and that each argument is put onto the stack using only as many bytes as it needs. Another platform might specify that arguments are pushed onto the stack left to right or that arguments are padded up to the next multiple of four bytes, to keep the stack nicely aligned. Many modern platforms specify that integer arguments under a certain size are passed in general registers, floating-point arguments are passed in floating-point registers, and other arguments are passed on the stack.
When printf
sees %f
and looks for a double
argument, but you have passed an int
, what does printf
find? If this platform pushes both arguments onto the stack, then printf
finds the bits for your int
, but it interprets those bits as if they were a double
. This results in printf
printing a value determined by your int
, but it bears no obvious relationship to your int
because the bits have entirely different meanings in the encodings for int
and for double
.
If this platform puts an int
argument in one place but a double
argument in another place, then printf
finds some bits that have nothing at all to do with your int
argument. They are bits that just happened to be left over in, for example, the floating-point register where the double
argument should be. Those bits are just the residue of previous work. The value you get will be essentially random with respect to the int
you have passed. You can also get a mix, with printf
looking for eight bytes of a double
by taking four bytes of the int
you passed along with four bytes of whatever else was nearby.
When you run the program multiple times, you will often see the same value printed. This happens for two reasons. First, computers are mechanical. They operate in largely deterministic ways, so they do the same things over and over again, even if those things were not particularly designed to be used the way you are using them. Second, the environment the operating system passes to the program when it starts is largely the same each time you start the program. Most of its memory is either cleared or is initialized from the program file. Some of the memory or other program state is initialized from other environment in the computer. That data can be different from run to run of the program. For example, the current time obviously changes from run to run. So does your command history, plus values the command shell has placed in its environment variables. Sometimes running a program multiple times will produce different results.
When you use code whose behavior is not defined by some specification (which may be the C specification, a compiler specification, a machine and operating system specification, or other documents), then you cannot rely on the behavior of that code. (It is possible to rely on the behavior of code compiled by a particular C compiler that is specified by that C compiler even though it is not fully specified by the C standard.)