7

I'm using Visual Studio TC compiler for Little Endian. The following is the piece of code:

void main()
{    
    float c = 1.0;
    int a = 0x3F800000;
    int *ptr = (int *)&c;
    printf("\n0x%X\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    return;    
}

The output is:

0x3F800000
a = 0.000000
c = 1.000000

Float value 1.0 is 0x3F800000 and stored as 00 00 80 3F in memory for Little Endian. The same value is assigned to int a. How printf prints 0.000000 for int a while 1.000000 for float c? I've seen it prints all integer values as 0.000000, when printed using %f in printf.

Also, since printf is variable argument function, how does it knows whether the passed value in the register is int or float?

unwind
  • 391,730
  • 64
  • 469
  • 606
bugger
  • 169
  • 1
  • 1
  • 5
  • 2
    its because of undefined behaviour. – Koushik Shetty May 17 '13 at 11:23
  • 2
    giving a format specifier of `%f` and giving an argument of type int. – Koushik Shetty May 17 '13 at 11:24
  • printf() doesn't guess the types of variables given as parameters. It just trusts the flags specified by the user. – yoones May 17 '13 at 11:25
  • 4
    Any `float` argument is promoted to `double` when it's passed to a function. To find out exactly what's happening, have the compiler produce assembly output and look at the differences in the code generated for each of the `printf()` statements. – Adam Liss May 17 '13 at 11:27
  • 2
    section 7.21.6/9 says **"If a conversion specification is invalid, the behavior is undefined. If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined."**. this is for fprintf but applies equally to printf. – Koushik Shetty May 17 '13 at 11:38
  • @yoones apparently it does check this [L1](http://stackoverflow.com/a/16608147/1825795). i'm tring to find the answer to how it does. EDIT i got it [Here](http://stackoverflow.com/questions/3105114/how-to-get-printf-style-compile-time-warnings-or-errors). – Koushik Shetty May 17 '13 at 11:55
  • @Koushik: “Undefined behavior” is not a cause of something. Undefined behavior is a **lack** of definition, so it is only a lack of cause for a desired behavior. The actual cause of undesired behavior depends on other things, such as details of the implementation that are not defined by the C standard. – Eric Postpischil May 17 '13 at 12:20
  • @EricPostpischil "lack of cause for a desired behavior". but undefined behavior can also be lack of cause for undesired behavior too right?. – Koushik Shetty May 17 '13 at 12:37
  • @Koushik: Undefined behaviour is always undesired as well, at least outside the lab. – DevSolar May 17 '13 at 13:27
  • `*ptr` causes UB by violating the strict-aliasing rule (as do all the "solutions" advising to write `*(float *)&a` or similar) – M.M Apr 07 '15 at 01:41

7 Answers7

6

My psychic powers tell me Adam Liss's comment is the right answer: float arguments are promoted to double, so the printf() function expects that to happen: It expects a 64-bit value on the stack, but gets 32 bits plus garbage data that happen to be zero.

If you increase the display precision, the display should be something like a = 0.00000000001.

This also means this should work:

void main()
{    
    double c = 1.0;
    long long a = 0x3FF0000000000000;
    long long *ptr = (long long *)&c;
    printf("\n0x%llX\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    return;    
}
Medinoc
  • 6,577
  • 20
  • 42
  • Medinoc Thanks a lot. This explains everything. – bugger May 17 '13 at 12:43
  • 1
    +1. I did spend half an hour trying to prove you wrong. Disassembly showed that `double c` gets created in an FPU register right away (as opposed to `float c`), so I suspected that to "fix" your result for the wrong reason. But even removing the `double` altogether still got the observed output for `a`. Then I suspected the special handling for 64bit integers by GCC... but when a plain `printf( "%f\n", 0x0, 0x3ff0000 )` gave `1.0` without any `double` or `long long` involved, I had to admit defeat. Looking at FPU registers was a wild goose chase. – DevSolar May 17 '13 at 13:34
5

I have compiled your code in gcc and the code generated is following:

movl    $0x3f800000, %eax
movl    %eax, -4(%ebp)
movl    $1065353216, -8(%ebp)
leal    -4(%ebp), %eax
movl    %eax, -12(%ebp)
movl    -12(%ebp), %eax
movl    (%eax), %eax
movl    %eax, 4(%esp)
movl    $LC1, (%esp)
call    _printf
movl    -8(%ebp), %eax
movl    %eax, 4(%esp)
movl    $LC2, (%esp)
call    _printf
flds    -4(%ebp)
fstpl   4(%esp)
movl    $LC3, (%esp)
call    _printf

This could give you a hint, that float parameters are not taken from the regular stack but rather from the stack of floating points... I would expect that there would be something random rather than 0...

V-X
  • 2,979
  • 18
  • 28
  • You know the floating point registers ST0, ST1 and so (these work like a stack. you push (load) a number from memory and all ST(x + 1) = STx and ST0 is set to your value)... – V-X May 17 '13 at 11:54
  • Sorry for deleting my comment, making yours seem to "hang". Your answer makes it seem as if there is a "regular" stack and a "float stack". This is not only wrong but x86 platform-specific, and IMHO isn't helpful with regards to the question. The fact that x86 handles float registers *like* a stack doesn't affect the observed behaviour. – DevSolar May 17 '13 at 11:56
  • I think that the answer to this question is platform specific. Of cause the behaviour is undefined. specifically on x86 platform, the behavior is caused by the fact, that floats are stored in float registers and everything else is in stack. – V-X May 17 '13 at 12:21
  • We *both* barked up the wrong tree, and Medinoc got it right: `%f` pops a (64-bit) `double` off the stack and prints that. On low-endian machines, (32-bit) `0x3F800000` converted to a (64bit) double isn't enough to show up with default (6 digit) precision. Try increasing precision, use a 64-bit type for `a`, or (and this is what convinced *me*) call `printf( "%f\n", 0, a )` to push the value of `a` to where it matters for `%f`. Brothers in defeat, +1 to Medinoc. (It's *still* UB, of course.) – DevSolar May 17 '13 at 13:23
5

As -Wall states: warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’. This is undefined behavior as also explained in more detail here.

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

So what you see here, is what the compiler builder decided to happen, which can be anything.

Community
  • 1
  • 1
Bort
  • 2,423
  • 14
  • 22
2

Within any C implementation, there are rules about how parameters are passed to functions. These rules may say that parameters of certain types are passed in certain registers (e.g., integer types in general registers and floating-point types in separate floating-point registers), that large arguments (such as structures with many elements) are passed on the stack or by a pointer to a copy of the structure, and so on.

Inside a called function, the function looks for the parameter it expects in the places that the rules specify. When you pass an integer in an argument to printf but pass it %f in the format string, you are putting an integer somewhere but telling printf to look for a float (that has been promoted to a double). If the rules for your C implementation specify that the integer argument is passed in the same place as a double argument, then printf will find the bits of your integer, but it will interpret them as a double. On the other hand, if the rules for your C implementation specify different places for the arguments, then the bits of your integer are not where printf looks for the double. So printf finds some other bits that have nothing to do with your integer.

Also, many C implementations have 32-bit int types and 64-bit double types. The %f specifier is for printing a double, not a float, and the float value you pass is converted to double before calling the function. So, even if printf finds the bits of your integer, there are only 32 bits there, but printf uses 64. So the double that is printed is made up of 32 bits you passed and 32 other bits, and it is not the value you intended to print.

This is why the format specifiers you use must match the arguments you pass.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

I meet the similar issue , and finally I develop a way to solve it , not sure whether that's what you want.The key point is that : you should pass a float instead of a integer .

#include <stdio.h>
void printIntAsFloat();

int main(void){
    printIntAsFloat();
}

void printIntAsFloat(){
    float c = 1.0;
    int a = 0x3F800000;
    int *ptr = (int *)&c;
    float *fp = (float*)((void*)&a); /*this is the key point*/
    printf("\n0x%X\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    printf("\nfp = %f", *fp);
    return; 
} 

the output is like this :

0x3F800000

a = 0.000000
c = 1.000000
fp = 1.000000

OS : Fedora21 64 bit GCC version :gcc version 4.9.2 20141101 (Red Hat 4.9.2-1) (GCC)

Chris.Huang
  • 545
  • 1
  • 4
  • 9
  • This still has undefined behaviour, all the same original issues remain as were discussed in the earlier answers and comments. What you did is stumbled on a combination of planetary alignment that appears to work just now, on one system with one compiler version and combination of settings. – M.M Apr 07 '15 at 01:43
  • I am not so familiar with C , can you please explain which line of code contain undefined behavior slightly ? – Chris.Huang Apr 07 '15 at 03:28
  • Lines 13,14,16 do; and lines 11 and 12 might do. I don't want to repeat what's already been said though, so read over other comments. – M.M Apr 07 '15 at 03:40
  • you are right . I just realize what i did is exactly same as others has discussed before . – Chris.Huang Apr 07 '15 at 03:55
-1

cast the variables

printf("\na = %f", (float)a);
printf("\nc = %f", (float)c);
mf_
  • 605
  • 8
  • 17
-1
int a= 0x3F800000;
printf("\na = %f", *(float*)&a);//1.0
BLUEPIXY
  • 39,699
  • 7
  • 33
  • 70