4

When I executed this code snippet:

void main(){
    float a=3;
    int b=5;
    printf( "%d %d \n", a, b);
}

I got output as

0 1074266112 

and when i changed the order of printing i.e.,

printf("%d %d \n",b,a);

I got this as output:

5 0

Why?

Vladimir Panteleev
  • 24,651
  • 6
  • 70
  • 114
Anks
  • 37
  • 6
  • 8
    Undefined behaviour, anything could happen. – Oliver Charlesworth Apr 18 '14 at 08:25
  • 3
    Moreover, how did you manage to get two lines of output in the first case. – devnull Apr 18 '14 at 08:25
  • @devnull sorry for the second line output, by mistake i wrote that – Anks Apr 18 '14 at 08:27
  • @MadHatter Why wrong..?? – Anks Apr 18 '14 at 08:32
  • 1
    You can't use `%d` to print a `float`. `printf` uses the format specifier to infer the type of the corresponding argument. If those types mismatch, you get "Undefined behaviour, anything could happen". – Brave Sir Robin Apr 18 '14 at 08:37
  • @rmartinjak yes you are right, I cannot de-promote something but I am not bothered about 'a' value, Check 'b' and please read the question again – Anks Apr 18 '14 at 08:43
  • @anks "Anything could happen" corresponds not only to the part of the program that invokes UB, but the whole program after that point in time. – Brave Sir Robin Apr 18 '14 at 09:07
  • 1
    @anks in your case, it might well be that a) float and int are 4 bytes, double 8; b) `a` is promoted to double (this is guaranteed); c) `printf` tries to grab two ints from wherever the arguments are stored, interpreting the first 4 and the last 4 bytes of `(double)a` as integers, printing "garbage". But that's only speculation. – Brave Sir Robin Apr 18 '14 at 09:10

6 Answers6

3

As your specifiers don't match the actual values, you invoke undefined behaviour. That means just anything can happen and, especially, the behaviour could change between invocations of the program (if the format specifiers read more data than actually provided) or at least between compiler settings.


What internally probably happens depends on many factors, such as the length of int values and much more. In any case, it is something you cannot rely on.

What really happens here is: floats are automatically promoted to double when being passed to a variadic function, changing their length to 8 bytes.

I modified your program in this way:

#include <stdio.h>

void main(){
    float a=3;
    int b=5;
    printf("%08x %08x %08x\n", a, b);
    printf("%08x %08x %08x\n", b, a);
    printf("%d %d %d\n", a, b);
    printf("%d %d %d\n", b, a);
}

which gives the output

00000000 40080000 00000005
00000005 00000000 40080000
0 1074266112 5
5 0 1074266112

So we exactly see the values resp. bytes being passed via the stack to printf(). As these values are swapped due to the endianness (they are visually swapped when interpreted via %08x), I really have the bytes

00 00 00 00  00 00 08 40  05 00 00 00
05 00 00 00  00 00 00 00  00 00 08 40

If we now use the wrong specifiers, we get the mapping

00 00 00 00 -> 00000000 -> 0
00 00 08 40 -> 40080000 -> 1074266112
05 00 00 00 -> 00000005 -> 5

which is then output.

If I omit one %d, the respectively last value is omitted as well, leading to

0 1074266112
5 0

in turn.

So the reason why your b value seems to change is that in the first case, you really get the "other" part of your a value.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • Didn't get you..? Please go through the question once again – Anks Apr 18 '14 at 08:40
  • @user3293256 I did so. And I saw that you matched a `3.0f` with a `%d`, which is not defined. – glglgl Apr 18 '14 at 08:43
  • @user3293256 I now explain in detail what internally happens. – glglgl Apr 18 '14 at 09:01
  • @self When in memory I have `00 00 08 40` on a LE system, it is interpreted as `40080000`. I didn't "somehow" convert it, I explicitly did so in order to show what is really in memory when it displays `40080000`. – glglgl Apr 18 '14 at 09:09
  • @glglgl My bad :(, I didn't realize you were showing the actual memory. – this Apr 18 '14 at 09:17
  • @glglgl where i can find more supporting/readable stuff for this ? – Anks Apr 18 '14 at 10:02
  • There are draft versions of the C standard somewhere out there, there are as well good tutorials for learning C. Alas, I cannot give a concrete recommendation... – glglgl Apr 18 '14 at 17:13
3

You code does technically invoke undefined behaviour; still there is clear logic behind the two outputs.

It has to do with variadic function and automatic type promotions.

Everytime you pass a float to a variadic function it gets automatically promoted to double. The printf specifier %f is always interpreted as %lf. you can try this yourself, output is always double.

First example:

float a=3;
int b=5;
printf( "%d %d \n", a, b);

( int type takes 4 bytes on you system )

You pass a float which gets promoted to a double and takes 8 bytes of stack, and a integer which takes 4 bytes. Then you try to print the first 8 bytes on the stack( with %d of 4 bytes twice ) and you get the first 4 and last 4 bytes of the ( now )double. The integer in not read.

Second example:

printf( "%d %d \n", a, b);

This is different as you first pass the integer which gets printed correctly and then you only print the first 4 bytes of the double.

this
  • 5,229
  • 1
  • 22
  • 51
  • "%f is always interpreted as %lf" is a funny way of putting it; `%f` has always been the printf specifier for `double` since day one. `%lf` was added in C99. – M.M Apr 18 '14 at 09:20
  • and since then it is used for float. – this Apr 18 '14 at 09:21
  • ...and for double. In C99 the `l` is specified as having no effect; previously it was undefined. – M.M Apr 18 '14 at 09:24
  • ..and your point is? Don't bother answering, it is a rhetorical question. – this Apr 18 '14 at 13:04
2

To answer your question, format specifiers in a printf call don't affect each other, rather they determine how printf interprets the value of the corresponding argument passed to it. This is true for a scanf call as well.

Using wrong format specifier for a value is undefined behaviour. Therefore, %d conversion specifier for the float variable a is wrong here.

float a = 3;
int b = 5;

// undefined behaviour due to %d for a
printf( "%d %d \n", a, b); 

Undefined behaviour means the behaviour of the code is unpredictable. Anything can happen from random output to program crash to your hard drive getting formatted. In short, the behaviour cannot be reasoned out and you should always avoid writing such code. For more, please read these -

Community
  • 1
  • 1
ajay
  • 9,402
  • 8
  • 44
  • 71
2

First a couple of facts.

  1. On a 32-bit system, when you specify "%d %d", printf expects to get 4 bytes for the first int and then 4 bytes for the second int.
  2. When you pass a float to printf, it is automatically promoted to double, which means that it takes 8 bytes on the stack.

Now to your question.

In the first case where you call printf( "%d %d \n", a, b);, the program will push 12 bytes onto the stack, 8 bytes for float a and 4 bytes for int b. Because you specified %d %d, printf will take the first 4 bytes of a and print that as the first number 0. It will then take the second 4 bytes of a and print that as the second number 1074266112.

In the second case where you call printf("%d %d \n",b,a);, the program will again push 12 bytes onto the stack, but this time the 4 bytes of b are first, followed by the 8 bytes for a. So printf displays b correctly, and then displays the first 4 bytes of a as the second number 0.

user3386109
  • 34,287
  • 7
  • 49
  • 68
0

%d for integers.

printf ( "floats: %4.2f %+.0e %E \n" , 3.1416, 3.1416, 3.1416); 
tas
  • 472
  • 3
  • 6
0

When the cast of the argument is carried out to an integer, delivery of the value by the floating point library or a calculation function is not performed. Therefore, it becomes a value different from assumption. For example, if the case where it is the program which you showed runs by the x86 architecture, only the value by which the cast was carried out by the mmx command will come to be displayed correctly. If the existence of this operation is using gcc, it is good to give "-S" option.