-2

printf("%f", 20); results in the output 0.000000, not 20.000000. I'm guessing this has to do with how an int is represented in memory and how a double is represented in memory. Surprisingly for me, no matter how I alter 20, for example by making the number larger, the output is still 0.000000. Could someone please explain the underlying mechanics of this?

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
Sahand
  • 7,980
  • 23
  • 69
  • 137
  • 1
    It's undefined behaviour. – Jabberwocky Jul 11 '17 at 12:31
  • A possible reason is that `double` is larger than `int` (e.g. 8 bytes vs 4 bytes) on your platform. But there are **many** possible reasons for behavior you see when your code's behavior is **undefined**. –  Jul 11 '17 at 12:33
  • minus for asking ridiculous questions, not worth to answer. if you are curious what did not you take a look on the assembly listing, which can be generated by the most of the compilers. – 0___________ Jul 11 '17 at 13:42

3 Answers3

4

First of all, this is undefined behavior. %f expects an argument of type float/double. Passing an int makes incompatible type and hence it invokes UB.

Quoting C11, chapter §7.21.6.1, fprintf()

f,F

A double argument representing a floating-point number [...]

a float is also allowed, as due to default argument promotion rule, it will get promoted to a double which is the expected type there, so either a double or a float is acceptable, but not an int.

...and that's all. UB, is, well, UB. You cannot try to justify anything with a code producing UB.


That said, with proper warning levels enabled, the code should not compile, at all. Though, if you choose to make the code compile and produce a binary/assembly code you can see different code generated for different platforms. One of such cases is explained in the other answer by Matteo Italia, considering x86_64 arch on Linux/OS X.

Community
  • 1
  • 1
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • And if OP turned on their compiler's warnings and errors, it wouldn't even compile! – John Zwinck Jul 11 '17 at 12:31
  • @JohnZwinck it should not, yes, agree. – Sourav Ghosh Jul 11 '17 at 12:32
  • It might be worth noting that `%f` specifically expects to receive a `double`, not a `float`, and that varargs rules make that happen when a `float` is passed. So before anyone thinks that passing `int` where `float` is expected isn't really so terrible, think again, because `int` is actually being passed instead of `double`, and those two are not the same size on most platforms. – John Zwinck Jul 11 '17 at 12:33
  • I'm interested in what happens though. For example, I can write `printf("%c", 100)` and get `d`printed out, even though I entered an int. So surely, even though it is undefined behaviour, `printf("%f", 20)` points to the memory location where the `int` is stored? If so, one should be able to explain what is going on under the hood. EDIT: This was in response to Sourav Gosh's initial answer. – Sahand Jul 11 '17 at 12:34
  • @Sandi what's the problem with `printf("%c", 100)`? it's a valid statement, no UB. – Sourav Ghosh Jul 11 '17 at 12:36
  • 1
    @Sandi 100 is a perfectly valid value for a `char` so that isn't UB at all. – Chris Turner Jul 11 '17 at 12:36
  • @Sandi What's going on under the hood is that floating point arguments are passed differently than integer arguments. If you're on a relatively modern architecture function arguments are passed in registers and completely different registers are used for normal values and floating point. – Art Jul 11 '17 at 12:37
  • 1
    `printf("%c", 100)` is equivalent to printf("%c", 'd');. i.e. both `100` and `d` are within the range of a valid `char` (0-255) – ryyker Jul 11 '17 at 12:38
  • .......and `%c` actually expects and `int` type argument, which is ___"converted to an unsigned char, and the resulting character is written."___ – Sourav Ghosh Jul 11 '17 at 12:39
  • @sandi But that's under the hood. The correct answer is UB because peeking under the hood is dangerous until you really know what you're doing (and that often leads to not needing to peek under the hood). – Art Jul 11 '17 at 12:39
  • 2
    Yup, understood, Art. The point of this question, however, is to learn more about C and computers, not to take wild risks in programming. – Sahand Jul 11 '17 at 12:40
  • 1
    @Art - Depending on a person's learning style, sometimes peeking under the hood is _precisely how_ to become knowledgeable about what you are doing. :) – ryyker Jul 11 '17 at 12:41
  • 1
    @ryyker Absolutely. That's how I learn. That's also how I learned that it's dangerous. Explain the dark art, follow by "don't do this at home". – Art Jul 11 '17 at 12:47
4

Most probably you are compiling your code on a platform/ABI where even for varargs functions data is passed into registers, and in particular different registers for integer/floating point values. x86_64 on Linux/OS X behaves like that.

The caller has an integer to pass, so it puts it into rsi; on the other side, printf expects a floating point value, so it tries to read it from xmm0. No matter how you change your integer argument to any other value printf will be unaffected - if will just print whatever happens to stay into xmm0 at the moment of the call.

You can actually check if this is the case by changing your call to:

printf("%f", 20, 123.45);

if it's working as I described, you should see 123.45 printed (the caller here puts 123.45 into xmm0, as it is the first floating point parameter passed to the function; printf behaves as before, but this time finds another value into xmm0).

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • Thanks, this is the kind of answer I was looking for! – Sahand Jul 11 '17 at 12:39
  • Out of curiosity, on what platform are you compiling? – Matteo Italia Jul 11 '17 at 12:40
  • I'm on a 2,5 GHz Intel Core i7 Macbook Pro. – Sahand Jul 11 '17 at 12:41
  • Ok, so most probably it's like I said; OS X follows the System V ABI, which works as I described. – Matteo Italia Jul 11 '17 at 12:43
  • Okay. Could you point me in the direction of literature where I can learn more about these things? – Sahand Jul 11 '17 at 12:43
  • @Sandi The documentation for how functions are handled on your particular system is here: https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf But it's not very easy reading for a beginner. – Art Jul 11 '17 at 12:49
  • 1
    Heh, difficult to say; I mostly learnt this stuff from debugging (live and post mortem) and doing reverse engineering, plus reading around on the internet (all the details of the System V ABI are published) and digging in the Intel manuals. Also, there was a nice book called "Reversing" that helped me getting started a few years ago. – Matteo Italia Jul 11 '17 at 12:51
0

The problem is your compiler is assuming the 20 is an int. Your options are to either declare a float variable and input it here OR add a typecast.

e.g.

printf("%f", (double)20);
  • 3
    It's not "probably", the compiler has to treat it as an int because otherwise it wouldn't be C. Also, float is incorrect because `%f` requires a double argument. – Art Jul 11 '17 at 12:36
  • 1
    @Art *Also, float is incorrect because `%f` requires a double argument.* No. A `float` works just as well as a `double` because a `float` is promoted to a `double` for variadic arguments per default argument promotion. See **6.5.2.2 Function calls**, paragraph 6 of [the C Standard](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) and also https://stackoverflow.com/questions/1255775/default-argument-promotions-in-c-function-calls That's *why* there are no `float` format specifier strings. – Andrew Henle Jul 11 '17 at 14:12
  • 1
    @AndrewHenle yeah, I know. Just being pedantic. It's wrong from a readability point of view. Why cast to a type different than what the format specifier requires? – Art Jul 11 '17 at 14:22