Within any C implementation, there are rules about how parameters are passed to functions. These rules may say that parameters of certain types are passed in certain registers (e.g., integer types in general registers and floating-point types in separate floating-point registers), that large arguments (such as structures with many elements) are passed on the stack or by a pointer to a copy of the structure, and so on.
Inside a called function, the function looks for the parameter it expects in the places that the rules specify. When you pass an integer in an argument to printf
but pass it %f
in the format string, you are putting an integer somewhere but telling printf
to look for a float (that has been promoted to a double). If the rules for your C implementation specify that the integer argument is passed in the same place as a double argument, then printf
will find the bits of your integer, but it will interpret them as a double. On the other hand, if the rules for your C implementation specify different places for the arguments, then the bits of your integer are not where printf
looks for the double. So printf
finds some other bits that have nothing to do with your integer.
Also, many C implementations have 32-bit int
types and 64-bit double
types. The %f
specifier is for printing a double, not a float, and the float value you pass is converted to double before calling the function. So, even if printf
finds the bits of your integer, there are only 32 bits there, but printf
uses 64. So the double
that is printed is made up of 32 bits you passed and 32 other bits, and it is not the value you intended to print.
This is why the format specifiers you use must match the arguments you pass.