views:

79

answers:

1

Here is my short assembly program:

; This code has been generated by the 7Basic
; compiler <http://launchpad.net/7basic&gt;

extern printf

; Initialized data

      SECTION .data
f_0 dd 5.5
printf_f: db "%f",10,0

      SECTION .text

; Code

global main
  main:
push ebp
mov ebp,esp

push dword [f_0]
push printf_f
call printf
add esp,8

mov esp,ebp
pop ebp
mov eax,0
ret

What the program is supposed to do is print 5.5, but it prints:

-4101885043414705786563701568963176764603483495211119243453355953219830430011006780068899468997203661787555969981250050126586203424320244681571103387315766489883301796219461838644670607029711305942610787622864198879363376953745160639821663444829839767678538571371627347101810056161000273217639447052410683392.000000

What on earth am I doing wrong? The code is pushing the two arguments to printf() and then calling it. Nothing complicated.


Update: I was a little premature in thinking I had fixed this. I have updated the code.

+4  A: 

The instruction push f_0 pushes the address of f_0 on the stack, not the 5.5 in memory there, so the printf routine will take the address, plus the saved ebp (the next 4 bytes on the stack) and interpret the bits as a double and print it out. That turns out to be a very large number, as you see.

You need to load 8 bytes from f_0 and push those. something like

move eax, f_0
push dword ptr [eax+4]
push dword ptr [eax]

edit

You need to push 8 bytes as fp64 values are 8 bytes. fp64 is all that printf knows how to print -- in fact fp64 is all that C knows how to pass to functions or operate on. fp32 value can only be loaded from and stored to memory, but are always implicitly converted to fp64 (or larger) before being operated on. If you want to load an fp32 value, convert it fp64, and push it on the stack, you can use

fld dword ptr [f_0]
sub esp, 8
fstp qword ptr [esp]

This actually loads an fp32 value and converts it to fp80 (the x87's internal format), then converts that fp80 value to fp64 and stores it on the stack.

Chris Dodd
*[facepalm]* So **that's** why it was printing the huge number!
George Edison
`f_0` *is* an address. The only way to get the value stored there is to dereference it.
Gabe
@Gabe: I knew that's what he meant. The annoying thing? I did this once already :) Why am I loading 8 bytes?
George Edison
@Chris: I still can't get it to work, so I updated the code.
George Edison
The `printf` function assumes that any floating point arguments are `double` precision. This goes back to hardware implementation details on the original PDP hardware that C first ran on. Suffice it to say, you either want a `DQ` to declare the data or convert the value to a `double` before calling `printf`.
Gabe
@Gabe: How can I tell it to only print single precision floats?
George Edison
George: I'm pretty sure that `printf` does not have a standardized means of printing a `float` because C automatically converts `float` parameters to `double`. You can compile `float f = 5.5; printf("%f", f);` and disassemble the object code to see what happens.
Gabe
Chris: I'm pretty sure that C can pass a `float` to a function that it knows accepts a `float`, but in this case `printf` is a variadic (varargs) function so it doesn't know what the target types are supposed to be. Since it doesn't know that the function expects, it reverts to the old pre-prototype behavior where most everything was either an `int` or `double`.
Gabe
@Gabe: While the spec is only explicit about converting to 64 bits for functions without prototypes or variadic args (section 6.5.2.2 of the C99 spec), the fact that it exists (and historical precedent) means that pretty much all C-based ABIs pass all floats as doubles in all cases.
Chris Dodd
Chris: You're right that it is rare to find a C function that actually takes floats, but it is incorrect to say that C only knows how to pass doubles or larger.
Gabe