Try this:
printf("size of int = %d, size of float = %d, size of double = %d\n",
sizeof(int), sizeof(float), sizeof(double));
When you call printf(), the system pushes the arguments onto the stack. So the stack looks something like this:
pointer to format string [probably 4 bytes]
x [probably 4 bytes]
x [probably 4 bytes]
f [probably 6 or 8 bytes]
f [probably 6 or 8 bytes]
Then printf() pops bytes off the stack as it parses the format string. When it sees %d it pops enough bytes for an int, and when it sees %f it pops enough bytes for a float. (Actually, floats are promoted to doubles when they're passed as function arguments, but the important idea is that they require more bytes than ints.) So if you "lie" about the arguments it will pop the wrong number of bytes and blindly convert them according to your instructions.
So it will first pop the correct number of bytes for xd because you've correctly told it that x is an int.
But then it will pop enough bytes for a float, which will consume the second x and part of the first f from the stack, and interpret them as a float for xf.
Then it will pop off enough bytes for another float, which will consume the remainder of the first f and part of the second f, and interpret them as a float for ff.
Finally, it will pop off enough bytes for an int, which will consume the remainder of the second f, and interpret them as an int for fd.
Hope that helps.