3

When using a int to float implicit conversion, it fails with printf()

#include <stdio.h>

int main(int argc, char **argv) {
    float s = 10.0;
    printf("%f %f %f %f\n", s, 0, s, 0); 
    return 0;
}

when compiled with gcc -g scale.c -o scale it does output garbage

./scale
10.000000 10.000000 10.000000 -5486124068793688683255936251187209270074392635932332070112001988456197381759672947165175699536362793613284725337872111744958183862744647903224103718245670299614498700710006264535590197791934024641512541262359795191593953928908168990292758500391456212260452596575509589842140073806143686060649302051520512.000000

If I explicitly cast the integer to float, or use a 0.0 (which is a double) it works as designed.

#include <stdio.h>

int main(int argc, char **argv) {
    float s = 10.0;
    printf("%f %f %f %f\n", s, 0.0, s, 0.0); 
    return 0;
}

when compiled with gcc -g scale.c -o scale it does output the expected output

./scale
10.000000 0.000000 10.000000 0.000000

What is happening ?

I'm using gcc (Debian 10.2.1-6) 10.2.1 20210110 if that's important.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Steve Schnepp
  • 4,620
  • 5
  • 39
  • 54
  • 2
    https://stackoverflow.com/questions/59533345/conversion-type-in-printf-with-no-casting-operator – Mat Jun 13 '23 at 19:36
  • 4
    Because there is no implicit conversion from `int` to `double` for arguments passed to a variadic function (although there is for your `float` to `double`). The compiler isn't required to know what format type is expected: that's not known until run time, and not by the caller, and the compiler generates code to pass `0` which is an `int` value, although a compiler may issue a *warning*. My compiler issues two warnings. – Weather Vane Jun 13 '23 at 19:40
  • 1
    I recommend using this tool to understand/verify printf syntax: https://eisenwave.github.io/cdecl-plus/?decl=printf(%22%25f%20%25f%20%25f%20%25f%5Cn%22,%20a,%20b,%20c,%20d) – Jan Schultke Jun 13 '23 at 20:08

2 Answers2

6

The conversion specifier f expects an object of the type double. Usually sizeof( double ) is equal to 8 while sizeof( int ) is equal to 4. And moreover integers and doubles have different internal representations.

Using an incorrect conversion specifier results in undefined behavior.

From the C Standard (7.21.6.1 The fprintf function)

9 If a conversion specification is invalid, the behavior is undefined.275) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

As for objects of the type float then due to default argument promotions they are converted to the type double.

From the C Standard (6.5.2.2 Function calls)

6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

So these calls of printf

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

and

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

are equivalent relative to the result.

Pay attention to that some programmers to output doubles use the length modifier l in conversion specification as for example %lf. However the length modifier has no effect and should be removed.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Oh, that's actually why. sizeof(double) == 8 whereas sizeof(int) == sizeof(float) == 4. Which is why I wasn't understanding, as 0 and 0.0f should be bitwise equivalent otherwise. – Steve Schnepp Jun 13 '23 at 20:05
  • 2
    In your answer, you wrote: `"However the length modifier has no effect and should be removed."` -- I disagree with your statement that it "should be" removed. For example, one reason why programmers may prefer to use `%lf` instead of `%f` for `double` is that it works for both `scanf` and `printf`. Therefore, I believe it would be more appropriate to write "can be" instead of "should be". – Andreas Wenzel Jun 13 '23 at 20:50
  • @AndreasWenzel Using the length modifier that has no effect will only confuse readers of the code because they will think that either the author of the code does not know that the modifier has no effect or that the modifier is indeed necessary. – Vlad from Moscow Jun 13 '23 at 21:00
  • Coding style is a matter of taste, and should be handled as such. The is no "one and only". – DevSolar Jun 14 '23 at 19:11
  • @DevSolar This has nothing common with the coding style. You should not use constructions that have no meaning and effect. As there is written in the C Standard "has no effect on a following a, A, e, E, f, F, g, or G conversion specifier". So using the length modifier l with the conversion specifier f just demonstrates a bad knowing of the C Standard and nothing more. – Vlad from Moscow Jun 14 '23 at 20:04
  • @VladfromMoscow Well, that is your taste in the matter. ;-) Curly braces around one-liners or indentation also have no effect... – DevSolar Jun 14 '23 at 21:42
  • @DevSolar You are mistaken. If you will use C constructions that does not have any effect you will be said "Go and learn C". And this will be correct. That has nothing common with programming styles. – Vlad from Moscow Jun 14 '23 at 21:57
  • @VladfromMoscow Seems you have a strong taste in the matter. Doesn't change the fact that it's just a taste, and others (like Andreas and myself, plus the C committee I might add) disagreeing. You insisting that your taste is "the one and only coding style" shows that *you* have something to learn yet. – DevSolar Jun 15 '23 at 05:20
  • @DevSolar I do not know why you have mentioned the C committee but in the C Standard there is clear written that the length modifier has no effect. It only confuses readers of the code that will think either that the author of the code does not know the specification of the function fprintf or that the length modifier is indeed required and without it the code will be incorrect. – Vlad from Moscow Jun 15 '23 at 05:28
  • @VladfromMoscow Well, tastes differ. The language was extended in C99 to include this length modifier, so people are free to use it as they see fit. – DevSolar Jun 15 '23 at 05:30
  • @DevSolar It looks similarly if to writ for example int a[N]; memset( ( void * )a, 0, n * sizeof( int ) );. The first question of who will look this code will be: do you know that a pointer to an object type can be implicitly converted to the type void * do not you? – Vlad from Moscow Jun 15 '23 at 13:04
  • @DevSolar The same situation with using the specification lf in a call of printf. Usually beginners do not know that the length modifier l in this case has no effect. So a question arises: do you know that the length modifier l has no effect? So it is not a question of a style . It is a question of the qualification of programmers who write such a code that arises questions.. – Vlad from Moscow Jun 15 '23 at 13:04
  • @DevSolar The less qualifiaction of a programmer the more frequantly he writes code that arises questions. – Vlad from Moscow Jun 15 '23 at 13:06
  • @VladfromMoscow You don't seem to get it. Your arguments are valid for a certain coding style. Other people will and do disagree and come up with a different coding style, for reasons of their own that they will be able to argue for just as well and validly. The problem arises when you hold your opinion to be the only valid one and start calling those disagreeing with you less qualified. This will not make you, or anyone acting like that, friends in the team. Relax. In the end, you will have to go with whatever coding style is already established by the team you are joining. – DevSolar Jun 15 '23 at 15:35
4

A variadic function needs some kind of information on the number and type of arguments it was passed, as it has to actively retrieve those using va_arg (which requires the type of the argument to retrieve as a parameter). In the case of printf, this information is in the first parameter, the format string.

If there is a %f in the format string, the variadic function will expect a double argument1, and will retrieve this using va_arg.

But the compiler has no idea about this2. It does not take the format string into account -- all it sees is the actual type of the argument, in this case an int.

So there was an int put in, and a double1 taken out. This happens to be undefined behavior.

Your observed behavior is because your first platform passes floating point values differently than integer values -- the integer 0 got put in one place, but va_arg read from another -- reading, in fact, the second actual float1 argument the first time, and whatever was in the place a third float1 value would have been put in had you passed one.


1 float arguments to variadic functions are automatically expanded to double.

2 It could, and modern compilers can generate a warning if the format string and the argument types mismatch. But the compiler is not allowed to change the generated code depending on the format string, as that would make printf behave differently than e.g. a custom variadic function.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
DevSolar
  • 67,862
  • 21
  • 134
  • 209