89

The statement

printf("%f\n",0.0f);

prints 0.

However, the statement

printf("%f\n",0);

prints random values.

I realize I'm exhibiting some kind of undefined behaviour, but I can't figure out why specifically.

A floating point value in which all the bits are 0 is still a valid float with value of 0.
float and int are the same size on my machine (if that is even relevant).

Why does using an integer literal instead of a floating point literal in printf cause this behavior?

P.S. the same behaviour can be seen if I use

int i = 0;
printf("%f\n", i);
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 38
    `printf` is expecting a `double`, and you're giving it an `int`. `float` and `int` may be the same size on your machine, but `0.0f` is actually converted to a `double` when pushed into a variadic argument list (and `printf` expects that). In short, you're not fulfilling your end of the bargain with `printf` based on the specifiers your using and the arguments you're providing. – WhozCraig Jul 26 '16 at 18:24
  • most probably because the size of int is not equal to the size of double – Ryan Jul 26 '16 at 18:24
  • 22
    Varargs-functions don't automatically convert function arguments to the type of the corresponding parameter, because they can't. The necessary information is not available to the compiler, unlike non-varargs functions with a prototype. – EOF Jul 26 '16 at 18:25
  • 3
    Oooh... "variadics." I just learned a new word ... – Mike Robinson Jul 26 '16 at 18:29
  • @self: The difference in size might explain why the OP is seeing "random values" (actually arbitrary, not random), but the sizes have nothing to do with the fact that the behavior is undefined. – Keith Thompson Jul 26 '16 at 18:33
  • @MikeRobinson: Be aware that it's only a colloquialism. – Lightness Races in Orbit Jul 26 '16 at 18:34
  • You used the "implicit-cast" tag. You might want to read the [tag wiki](https://stackoverflow.com/questions/tagged/implicit-cast). (I changed the tag to "implicit-conversion", though there actually is no implicit conversion on `printf("%f\n", 0)`. – Keith Thompson Jul 26 '16 at 18:45
  • 2
    Possible duplicate: [**Can printf result in undefined behavior?**](http://stackoverflow.com/questions/23287222/can-printf-result-in-undefined-behavior) – Khalil Khalaf Jul 26 '16 at 18:46
  • Detail: `0`, `0.0` in C, are an _integer constant_ and a _floating point constant_. Not _literal_. C does have _string literals_ and _compound literal_. – chux - Reinstate Monica Jul 26 '16 at 18:48
  • @FirstStep Not really. The references mentioned here are fairly better than in the proposed dupe. – πάντα ῥεῖ Jul 26 '16 at 18:51
  • @πάνταῥεῖ Oh .. well thank you for pointing that out. I posted it before my page reloads so I just saw all these comments and answers.. or I just wanted to be part of this.. I don't know yet. – Khalil Khalaf Jul 26 '16 at 18:53
  • That's not a reason it's not a dupe. The reason it's not a dupe is because the types and values are different. – Lightness Races in Orbit Jul 26 '16 at 18:53
  • 1
    @Lightness _"is because the types and values are different"_ Well, we often expect the ability of abstraction to extrapolate such stuff from the OP, when marking dupes. But I'd not going to mark as a dupe if I see that there are already better answers at the current question (may be better vice versa). – πάντα ῥεῖ Jul 26 '16 at 19:03
  • 3
    The next thing to try is to pass a `(uint64_t)0` instead of `0` and see whether you still get random behavior (assuming `double` and `uint64_t` have the same size and alignment). Chances are the output will still be random on some platforms (e.g. x86_64) due to different types being passed in different registers. – Ian Abbott Jul 26 '16 at 20:27
  • @πάνταῥεῖ: In this case, the value is absolutely core to the question. – Lightness Races in Orbit Jul 26 '16 at 20:59
  • Possible duplicate of [Why printf() isn't outputting this integer as float number?](http://stackoverflow.com/questions/27543489/why-printf-isnt-outputting-this-integer-as-float-number) – J.J. Hakala Jul 27 '16 at 12:24
  • @IanAbbott Passing arguments in registers to printf? Possible but unlikely. – Peter - Reinstate Monica Jul 27 '16 at 12:55
  • @PeterA.Schneider That's the way its done for x86-64. The commonly used calling conventions (Microsoft x64 and System V AMD64) both use registers for the first few arguments, and the registers used depend on whether the arguments are floating point or not. See [x86-64 calling conventions](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions). – Ian Abbott Jul 27 '16 at 14:01
  • 2
    `gcc -Wall` would actually tell the reason immediately: `warning: format '%f' expects argument of type 'double', but argument 2 has type 'int' [-Wformat=] `. – ilkkachu Jul 27 '16 at 14:01
  • @IanAbbott When type and number of args are known, sure. For variadic functions, too? That should be part of the run time lib, i.e. how va_arg() and friends are implemented. Of course printf can be an intrinsic anyway etc... perhaps somebody has the time to check the assembler on one of the free compiler sites. – Peter - Reinstate Monica Jul 27 '16 at 15:00
  • @PeterA.Schneider Yes, for variadic functions too. The way Microsoft x64 handles variadic arguments is for the callee function to copy the registers holding variadic arguments into the 32-byte shadow space on the stack. This space is followed in memory by the remaining arguments on the stack, so `va_arg` sees all the arguments in order. Although floating point arguments are usually passed in separate registers, the caller also copies them to the corresponding non-floating point registers if the parameter type is unknown (as is the case for variadic arguments). – Ian Abbott Jul 27 '16 at 15:59
  • For even more variadic argument fun try using a literal `0` to zero-terminate a list of pointers. On a machine where `int` and `void*` are not the same size. – Zan Lynx Jul 27 '16 at 20:16
  • "However, the statement printf("%f\n",0); prints random values. " - really? Shouldn't it print one value only? Or are you running it in a loop (which is not specified in your question)? – Thomas Weller Jul 28 '16 at 07:35
  • @Thomas Sorry, that wasn't clear. It created arbitrary values each time I ran the program. – Trevor Hickey Jul 28 '16 at 13:19
  • @TrevorHickey Hi, the problem, I think depends by a cast carried by arg_va macro from a pointer at int type to double and consequent dereferencing.. see my answer for details – Ciro Corvino Aug 03 '16 at 13:44

10 Answers10

122

The "%f" format requires an argument of type double. You're giving it an argument of type int. That's why the behavior is undefined.

The standard does not guarantee that all-bits-zero is a valid representation of 0.0 (though it often is), or of any double value, or that int and double are the same size (remember it's double, not float), or, even if they are the same size, that they're passed as arguments to a variadic function in the same way.

It might happen to "work" on your system. That's the worst possible symptom of undefined behavior, because it makes it difficult to diagnose the error.

N1570 7.21.6.1 paragraph 9:

... If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

Arguments of type float are promoted to double, which is why printf("%f\n",0.0f) works. Arguments of integer types narrower than int are promoted to int or to unsigned int. These promotion rules (specified by N1570 6.5.2.2 paragraph 6) do not help in the case of printf("%f\n", 0).

Note that if you pass a constant 0 to a non-variadic function that expects a double argument, the behavior is well defined, assuming the function's prototype is visible. For example, sqrt(0) (after #include <math.h>) implicitly converts the argument 0 from int to double -- because the compiler can see from the declaration of sqrt that it expects a double argument. It has no such information for printf. Variadic functions like printf are special, and require more care in writing calls to them.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 13
    A couple of excellent core points here. First, that it's `double` not `float` so the OP's width assumption may not (probably doesn't) hold. Second, the assumption that integer zero and floating-point zero have the same bit pattern also does not hold. Good work – Lightness Races in Orbit Jul 26 '16 at 18:34
  • 1
    Your answer begs the question so I'll just add the link here: [Why does printf() promote a float to a double?](http://stackoverflow.com/questions/28097564/why-does-printf-promote-a-float-to-a-double) – Lucas Trzesniewski Jul 26 '16 at 20:05
  • 2
    @LucasTrzesniewski: Ok, but I don't see how my answer begs the question. I did state that `float` is promoted to `double` without explaining why, but that wasn't the main point. – Keith Thompson Jul 26 '16 at 20:11
  • Keith, i'll beg the question with @LucasTrzesniewski. `printf()` is a function, **not** integral to the C language. are you saying that the C compiler has special hooks in it to watch out for `printf()` and then examine the first argument which is the control string? my C knowledge is a little old (post K&R ANSI) but i always that it was `%lf` for `double` and `%f` for single-precision `float`. but nonetheless, when the OP puts `0` in the argument, he is pushing an `int` onto the stack (or "passing" an `int` to the function). then `printf` examines the control string. – robert bristow-johnson Jul 26 '16 at 23:49
  • 2
    @robertbristow-johnson: Compilers don't need to have special hooks for `printf`, though gcc, for example, does have some so it can diagnose errors (*if* the format string is a literal). The compiler can see the declaration of `printf` from ``, which tells it that the first parameter is a `const char*` and the rest are indicated by `, ...`. No, `%f` is for `double` (and `float` is promoted to `double`), and `%lf` is for `long double`. The C standard says nothing about a stack. It specifies the behavior of `printf` only when it's called correctly. – Keith Thompson Jul 26 '16 at 23:53
  • in the olden daze, it was called "lint". i understand (i think best as an option) a lexigraphical and syntactical function that might tell you that you're not using `printf()` correctly (or any other stdlib function). but **normally** the C compiler is the only thing that knows that a `float` was passed to the function. the only way the function (like `sprintf()` or `vprintf()`) knows what species of animal is passed to it is through the control string that is the first argument. – robert bristow-johnson Jul 27 '16 at 01:01
  • 2
    @robertbristow-johnson: In the olden daze, "lint" often performed some of the extra checking that gcc now performs. A `float` passed to `printf` is promoted to `double`; there's nothing magical about that, it's just a language rule for calling variadic functions. `printf` itself knows via the format string what the caller *claimed* to pass to it; if that claim is incorrect, the behavior is undefined. – Keith Thompson Jul 27 '16 at 01:07
  • so you're just saying that `printf()` requires doubles to be passed for `%f` in the control string. and if you pass a 4-byte integer, `printf()` wouldn't know, right? – robert bristow-johnson Jul 27 '16 at 05:39
  • @robertbristow-johnson: Right. Or an integer of any size. – Keith Thompson Jul 27 '16 at 05:42
  • 1
    @KeithThompson I felt the "why" part was interesting enough to warrant a link to an explanation, as it's not a behavior you would expect to occur intuitively (I mean it's documented but without the reasoning behind it). Sure, it's not the main point of the question, but it's a major reason why the UB in this case doesn't appear to "just work". – Lucas Trzesniewski Jul 27 '16 at 07:48
  • 2
    Small correction: the `l` length modifier "has no effect on a following `a`, `A`, `e`, `E`, `f`, `F`, `g`, or `G` conversion specifier", the length modifier for a `long double` conversion is `L`. (@robertbristow-johnson might also be interested) – Daniel Fischer Jul 27 '16 at 19:29
  • Floating point arguments might be passed using different registers than integer registers, so might behave differently than if you passed the int `reinterpret_cast`ed to float. (E.g. In windows 64 it's `rcx` for integers and `xmm0` for floating point types). – Daniel Aug 03 '16 at 17:25
  • @Dani: Does that apply to variadic functions? If so, the `va_arg()` macro would have some extra work to do, since it would have to use type information to determine which register to grab. Which is certainly possible, but passing variadic functions on the stack would probably make things easier. – Keith Thompson Aug 03 '16 at 17:36
  • @KeithThompson: I checked linux gcc and it does apply: https://godbolt.org/g/CxPttG – Daniel Aug 03 '16 at 17:47
60

First off, as touched on in several other answers but not, to my mind, spelled out clearly enough: It does work to provide an integer in most contexts where a library function takes a double or float argument. The compiler will automatically insert a conversion. For instance, sqrt(0) is well-defined and will behave exactly as sqrt((double)0), and the same is true for any other integer-type expression used there.

printf is different. It's different because it takes a variable number of arguments. Its function prototype is

extern int printf(const char *fmt, ...);

Therefore, when you write

printf(message, 0);

the compiler does not have any information about what type printf expects that second argument to be. It has only the type of the argument expression, which is int, to go by. Therefore, unlike most library functions, it is on you, the programmer, to make sure the argument list matches the expectations of the format string.

(Modern compilers can look into a format string and tell you that you've got a type mismatch, but they're not going to start inserting conversions to accomplish what you meant, because better your code should break now, when you'll notice, than years later when rebuilt with a less helpful compiler.)

Now, the other half of the question was: Given that (int)0 and (float)0.0 are, on most modern systems, both represented as 32 bits all of which are zero, why doesn't it work anyway, by accident? The C standard just says "this isn't required to work, you're on your own", but let me spell out the two most common reasons why it wouldn't work; that will probably help you understand why it's not required.

First, for historical reasons, when you pass a float through a variable argument list it gets promoted to double, which, on most modern systems, is 64 bits wide. So printf("%f", 0) passes only 32 zero bits to a callee expecting 64 of them.

The second, equally significant reason is that floating-point function arguments may be passed in a different place than integer arguments. For instance, most CPUs have separate register files for integers and floating-point values, so it might be a rule that arguments 0 through 4 go in registers r0 through r4 if they are integers, but f0 through f4 if they are floating-point. So printf("%f", 0) looks in register f1 for that zero, but it's not there at all.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • 1
    Are there any architectures that use registers for variadic functions, even among those that use them for normal functions? I thought that was the reason that variadic functions are required to be properly declared even though other functions [except those with float/short/char arguments] can be declared with `()`. – Random832 Jul 26 '16 at 19:20
  • 3
    @Random832 Nowadays, the only difference between a variadic and a normal function's calling convention is that there _may_ be some extra data supplied to a variadic, such as a count of the true number of arguments supplied. Otherwise everything goes in exactly the same place it would for a normal function. See for instance section 3.2 of http://www.x86-64.org/documentation/abi.pdf , where the only special treatment for variadics is a hint passed in `AL`. (Yes, this means the implementation of `va_arg` is much more complicated than it used to be.) – zwol Jul 26 '16 at 19:29
  • @Random832: I always thought the reason was that on some architectures functions with a known number and type of arguments could be implemented more efficiently, by using special instructions. – celtschk Jul 27 '16 at 07:36
  • @celtschk You might be thinking of the "register windows" on SPARC and IA64, which were supposed to accelerate the common case of function calls with a *small number* of arguments (alas, in practice, they do just the opposite). They don't require the compiler to treat variadic function calls specially, because the number of arguments *at any one call site* is always a compile-time constant, regardless of whether the callee is variadic. – zwol Jul 27 '16 at 14:20
  • @zwol: No, I was thinking of the `ret n` instruction of the 8086, where `n` was a hard-coded integer, which therefore was not applicable for variadic functions. However I don't know if any C compiler actually took advantage of it (non-C compilers certainly did). – celtschk Jul 27 '16 at 15:16
  • Err ... s/hard-coded/compile-time/ — the constant was given as part of the instruction itself. – celtschk Jul 27 '16 at 15:22
  • @celtschk Oh, right, that. If I remember correctly, the original 16-bit Windows compiler did use that, and the 32-bit compiler may have as well, ... but, in both cases, only for `__stdcall` functions, which (also IIRC) could not be variadic. Callee-pops-arguments is a space win with a simple compiler, but with a smarter compiler it winds up being a net lose (look at the disassembly of what GCC does when you call several functions in a row, reusing some of the arguments). – zwol Jul 27 '16 at 18:16
13

Why does using an integer literal instead of a float literal cause this behavior?

Because printf() doesn't have typed parameters besides the const char* formatstring as the 1st one. It uses a c-style ellipsis (...) for all the rest.

It's just decides how to interpret the values passed there according to the formatting types given in the format string.

You would have the same kind of undefined behavior as when trying

 int i = 0;
 const double* pf = (const double*)(&i);
 printf("%f\n",*pf); // dereferencing the pointer is UB
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 3
    Some particular implementations of `printf` might work that way (except that the items passed are values, not addresses). The C standard doesn't specify *how* `printf` and other variadic functions work, it just specifies their behavior. In particular, there is no mention of stack frames. – Keith Thompson Jul 26 '16 at 18:30
  • A small quibble: `printf` does have *one* typed parameter, the format string, which is of type `const char*`. BTW, the question is tagged both C and C++, and C is really more relevant; I probably wouldn't have used `reinterpret_cast` as an example. – Keith Thompson Jul 26 '16 at 18:43
  • Just an interesting observation: Same undefined behaviour, and most likely due to identical mechanism, but with a small difference in detail: Passing an int as in the question, the UB happens *within* printf when trying to interprete the int as double - in your example, it happens already *outside* when dereferencing pf... – Aconcagua Jul 26 '16 at 18:54
  • @Aconcagua Added clarification. – πάντα ῥεῖ Jul 26 '16 at 18:58
  • This code sample is UB for strict aliasing violation, an entirely different problem to what the question is asking about. For example you completely ignore the possibility that floats are passed in different registers to integers. – M.M Feb 21 '18 at 03:44
13

Ordinarily when you call a function that expects a double, but you provide an int, the compiler will automatically convert to a double for you. That doesn't happen with printf, because the types of the arguments aren't specified in the function prototype - the compiler doesn't know that a conversion should be applied.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 4
    Also, `printf()` *in particular* is designed so that its arguments could be of any type. You must know which type is expected by each element in the format-string, and you must provide it correctly. – Mike Robinson Jul 26 '16 at 18:31
  • @MikeRobinson: Well, any primitive C type. Which is a very, very small subset of all possible types. – MSalters Jul 27 '16 at 07:47
12

Using a mis-matched printf() specifier "%f"and type (int) 0 leads to undefined behavior.

If a conversion specification is invalid, the behavior is undefined. C11dr §7.21.6.1 9

Candidate causes of UB.

  1. It is UB per spec and the compile is ornery - 'nuf said.

  2. double and int are of different sizes.

  3. double and int may pass their values using different stacks (general vs. FPU stack.)

  4. A double 0.0 might not be defined by an all zero bit pattern. (rare)

a3f
  • 8,517
  • 1
  • 41
  • 46
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
10

This is one of those great opportunities to learn from your compiler warnings.

$ gcc -Wall -Wextra -pedantic fnord.c 
fnord.c: In function ‘main’:
fnord.c:8:2: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
  printf("%f\n",0);
  ^

or

$ clang -Weverything -pedantic fnord.c 
fnord.c:8:16: warning: format specifies type 'double' but the argument has type 'int' [-Wformat]
        printf("%f\n",0);
                ~~    ^
                %d
1 warning generated.

So, printf is producing undefined behavior because you are passing it an incompatible type of argument.

wyrm
  • 318
  • 1
  • 12
9

I'm not sure what's confusing.

Your format string expects a double; you provide instead an int.

Whether the two types have the same bit width is utterly irrelevant, except that it may help you avoid getting hard memory violation exceptions from broken code like this.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • I would say calling a format string modifer `f` and then expecting a **d**ouble instead of a **f**loat would explain the confusion. Just one of those little things. – Voo Jul 26 '16 at 18:27
  • 3
    @Voo: That format string modifier _is_ unfortunately named, but I still don't see why you'd think that an `int` would be acceptable here. – Lightness Races in Orbit Jul 26 '16 at 18:27
  • The way I understand the question was that the OP assumed that the format string modifier took a float and then wondering why data with the same bit width (which would also qualify as a valid float pattern) would cause weird results. I mean passing a long (assuming 8byte longs on the architecture) would still be UB and result in weird behavior since longs and doubles are passed via different registers on many architectures, so no change there. – Voo Jul 26 '16 at 18:31
  • 1
    @Voo: _"(which would also qualify as a valid float pattern)"_ Why would an `int` qualify as a valid float pattern? Two's complement and various floating-point encodings have almost nothing in common. – Lightness Races in Orbit Jul 26 '16 at 18:32
  • We already established that this is UB, but in practice pretty much every architecture (yes I know the IBM exceptions and co) uses IEEE-754 for floats and 32 0s is a valid representation of 0.0f then. The reason this still causes troubles might be interesting to some, since usually passing `0` for a double works perfectly fine. – Voo Jul 26 '16 at 18:34
  • 2
    It's confusing because, for _most_ library functions, providing the integer literal `0` to an argument typed `double` will do the Right Thing. It is not obvious to a beginner that the compiler doesn't do that same conversion for `printf` argument slots addressed by `%[efg]`. – zwol Jul 26 '16 at 18:34
  • @Voo: But 32 zeroes doesn't take up _64_-bits, and the function is expecting a `double`. – Lightness Races in Orbit Jul 26 '16 at 18:36
  • @zwol: Okay, perhaps. I thought the OP had pre-indicated comprehension of this in the question (so my answer was predicated on that level of knowledge), but re-reading the question that's actually not clear. Still, a quick glance at the documentation would sort it. – Lightness Races in Orbit Jul 26 '16 at 18:37
  • @Lightness Yes, but even if you did pass 0L chances would still be that it wouldn't work as expected which would be the more interesting case (actually I don't know - do calling conventions for x86/64 pass the first few arguments for variadic functions on the stack or do they use registers as well? If they're all passed on the stack in practice 0L would even work..) – Voo Jul 26 '16 at 18:38
  • @Voo Point is it doesn't really matter. UB is UB is UB and [you could time travel](https://blogs.msdn.microsoft.com/oldnewthing/20140627-00/?p=633) before observing any of these implementation-specific happenstances. – Lightness Races in Orbit Jul 26 '16 at 18:39
  • I knew it was Raymond's post before even looking at the URL.. Yes yes we established UB, I doubt even the OP didn't know it was UB. Doesn't mean seeing the practical behavior of compilers isn't interesting, because that actually does teach you something interesting.# – Voo Jul 26 '16 at 18:40
  • 1
    @Voo: If you're interested in how horribly wrong this can go, consider that on x86-64 SysV ABI, floating-point arguments are passed in a different register-set than integer arguments. – EOF Jul 26 '16 at 18:43
  • @EOF I'd assume that even on x64 the float parameters are passed via MMX registers or similar, but I don't know. And that's exactly what I mean with "teach you something interesting" :-) – Voo Jul 26 '16 at 18:45
  • @Voo Well, the point is that even if the integer you pass has a representation that also works as a floating-point, `printf()` doesn't even *look* at those bits. It instead uses whatever garbage is left in the floating-point registers (or the stack). – EOF Jul 26 '16 at 18:47
  • @Eof I know (I did work on compilers for a living), my point is that just saying "It's UB, nothing interesting to see here" is selling the question short. Yes you want to mention that it's UB, but then you can go into all those way more interesting topics such as how variadic functions pass parameters and the different calling conventions and where parameters are stored. – Voo Jul 26 '16 at 18:49
  • Not on a question broadly tagged [tag:c++], you can't. If you want to ask about a specific implementation then fine, but that's not what this question is. – Lightness Races in Orbit Jul 26 '16 at 18:52
  • 1
    @LightnessRacesinOrbit I think it's _always_ appropriate to discuss _why_ something is UB, which usually involves talking about what implementation latitude is allowed and what actually happens in common cases. – zwol Jul 26 '16 at 18:57
  • @Lightness If you would like to open a meta post asking all those c++ optimization questions to be closed since performance is not specified anywhere by the standard (could someone please say "it's nonsense to talk about performance of a language, that's about the implementation"?), be my guest. But up to now, questions that go over the standard have been perfectly acceptable here and I don't see that changing any time soon. – Voo Jul 26 '16 at 19:23
  • @Voo: I completely agree. Such questions are more than welcome. This just isn't one of them. – Lightness Races in Orbit Jul 26 '16 at 20:56
  • @Voo on many systems `0L` is still 32 bits, and you would need `0LL`. – David Conrad Jul 27 '16 at 08:50
4

"%f\n" guarantees predictable result only when the second printf() parameter has type of double. Next, an extra arguments of variadic functions are subject of default argument promotion. Integer arguments fall under integer promotion, which never results in floating-point typed values. And float parameters are promoted to double.

To top it off: standard allows the second argument to be or float or double and nothing else.

Sergio
  • 8,099
  • 2
  • 26
  • 52
4

Why it is formally UB has now been discussed in several answers.

The reason why you get specifically this behaviour is platform-dependent, but probably is the following:

  • printf expects its arguments according to standard vararg propagation. That means a float will be a double and anything smaller than an int will be an int.
  • You are passing an int where the function expects a double. Your int is probably 32 bit, your double 64 bit. That means that the four stack bytes starting at the place where the argument is supposed to sit are 0, but the following four bytes have arbitrary content. That's what is used for constructing the value which is displayed.
glglgl
  • 89,107
  • 13
  • 149
  • 217
0

The main cause of this "undetermined value" issue stands in the cast of the pointer at the int value passed to the printf variable parameters section to a pointer at double types that va_arg macro carries out.

This causes a referencing to a memory area that was not completely initialized with value passed as parameter to the printf, because double size memory buffer area is greater than int size.

Therefore, when this pointer is dereferenced, it is returned an undetermined value, or better a "value" that contains in part the value passed as parameter to printf, and for the remaining part could came from another stack buffer area or even a code area (raising a memory fault exception), a real buffer overflow.


It can consider these specific portions of semplificated code implementations of "printf" and "va_arg"...

printf

va_list arg;
....
case('%f')
      va_arg ( arg, double ); //va_arg is a macro, and so you can pass it the "type" that will be used for casting the int pointer argument of printf..
.... 


the real implementation in vprintf (considering gnu impl.) of double value parameters code case management is:

if (__ldbl_is_dbl)
{
   args_value[cnt].pa_double = va_arg (ap_save, double);
   ...
}



va_arg

char *p = (double *) &arg + sizeof arg;  //printf parameters area pointer

double i2 = *((double *)p); //casting to double because va_arg(arg, double)
   p += sizeof (double);



references

  1. gnu project glibc implementation of "printf"(vprintf))
  2. example of semplification code of printf
  3. example of semplification code of va_arg
Community
  • 1
  • 1
Ciro Corvino
  • 2,038
  • 5
  • 20
  • 33