Reputation: 578
I am using Microsoft Visual Studio 2015 to learn inline assembly programming. I have read a lot of posts on stackoverflow including the most relevant one this, but after I tried the methods the result is still 0.0000. I used the float first and store the value to fpu but the reuslt is the same and tried passing value to eax and still no help.
Here is my code:
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
char input1[] = "Enter number: \n";
char input_format[] = "%lf";
double afloat;
char output[] = "The number is: %lf";
_asm {
lea eax, input1
push eax
call printf_s
add esp, 4
lea eax, input_format
push eax
lea eax, afloat
push eax
call scanf_s
add esp, 8
sub esp, 8
fstp [esp]
lea eax, output
push eax
call printf
add esp, 12
}
return 0;
}
Upvotes: 0
Views: 4739
Reputation: 578
Thank you for all the help and caveats offered. There are still problems I need to configure and fix, but after reading most float point operators on this website , I finally come up with a solution to read and display float point number with Microsoft Visual Studio. I am sorry for all the bad conventions I've used in this post and lack of comments, I will learn to program in a better style. Thanks again!
Here is my working code(different from original post since I want to use float point number there):
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
char input1[] = "Enter number: \n";
char input_format[] = "%f";
char output_format[] = "%.2f\n"; //for better ouput
float afloat;
char output[] = "The number is:";
_asm {
lea eax, input1
push eax
call printf_s
add esp, 4
call flushall; // flush all streams and clear all buffers
mov afloat, 0; // give the variable afloat a default
lea eax, afloat;
push eax;
lea eax, input_format; // read the floating number user inputs
push eax;
call scanf_s;
add esp, 8;
//leave space for more operations.
lea eax, output;
push eax;
call printf; // print the output message
add esp, 4;
sub esp, 8; // reserve stack for a double in stack
fld dword ptr afloat; // load float
fstp qword ptr[esp]; // IMPORTANT: convert to double and store, because printf expects a double not a float
lea eax, output_format; // import the floating number format '%f'
push eax;
call printf; // print the variable afloat
add esp, 12;
}
return 0;
}
Upvotes: 0
Reputation: 244732
You are attempting to print the wrong value. In fact, the code should just be causing nonsense to be printed to the terminal. You got quite lucky that you see 0.0
. Let's look specifically at the part of the code that retrieves the floating point value, which is your call to scanf_s
:
lea eax, input_format
push eax
lea eax, afloat
push eax
call scanf_s
add esp, 8
sub esp, 8
fstp [esp]
First of all, I don't see any logic in adding 8 to your stack pointer (esp
) and then immediately subtracting 8 from the stack pointer. Performing these two operations back-to-back just cancel each other out. As such, these two instructions can be deleted.
Second, you are pushing the arguments on the stack in the wrong order. The cdecl calling convention (used by the CRT functions, including printf_s
and scanf_s
) passes argument in reverse order, from right to left. Therefore, to call scanf_s
, you would first push the address of the floating-point value into which the input should be stored, and then push the address of the format control string buffer. Your code is wrong, because it pushes the arguments from left to right. You get lucky with printf_s
, because you're only passing one argument, but because you're passing two arguments to scanf_s
, bad things happen.
The third problem is that you appear to be assuming that scanf_s
returns the output directly.
If it did, and you had requested a floating point value, you would be correct that the cdecl
calling convention would have it returning that value at the top of the floating-point stack, in FP(0)
, and thus you would be correctly popping that value and storing it in a memory location (fstp [esp]
).
While scanf_s
does return a value (an integer value indicating the number of fields that were successfully converted and assigned), it does not return the value from the standard input stream. There is, in fact, no way that it could do this, since it supports arbitrary types of inputs. This is why it uses a pointer to a memory location to store the value. You probably knew this already, since you arranged to pass that pointer as a parameter to the function.
Now, why did you get an output of 0.0
in the final call to printf
? Because of the fstp [esp]
instruction. This pops the top value off of the floating-point stack, and stores it in the memory address contained in esp
. I have already pointed out that scanf_s
does not place any value(s) on the floating-point stack, so technically, it contains meaningless/garbage data. But in your case, you were lucky enough that FP(0)
actually contained 0.0
, so that is what got printed. Why does FP(0)
contain 0.0
? I'm guessing because this is a debug build that you're running, and the CRT is zeroing the stack. Or maybe because that's what FSTP
pops off the stack when the stack is empty. I don't know, and I don't see that documented anywhere. But it doesn't really matter what happens when you write incorrect code, because you should strive to only write correct code!
Here's what correct code might look like:
; Get address of 'input1' buffer, and push it onto the stack
; in preparation of a call to 'printf_s'. Then, make the call.
lea eax, DWORD PTR [input1]
push eax
call printf_s
add esp, 4 ; clean up the stack after calling 'printf_s'
; Call 'scanf_s' to retrieve the floating-point input.
; Note that in the __cdecl calling convention, arguments are always
; passed on the stack in *reverse* order!
lea eax, DWORD PTR [afloat]
push eax
lea eax, DWORD PTR [input_format]
push eax
call scanf_s
; (no need to clean up the stack here; we're about to reuse that space!)
; The 'scanf_s' function stored the floating-point value that the user entered
; in the 'afloat' variable. So, we need to load this value onto the top
; of the floating point stack in order to pass it to 'printf_s'
fld QWORD PTR [afloat]
fstp QWORD PTR [esp]
; Get the address of the 'output' buffer and push it onto the stack,
; and then call 'printf_s'. Again, this is pushed last because
; __cdecl demands that arguments are passed right-to-left.
lea eax, DWORD PTR [output]
push eax
call printf_s
add esp, 12 ; clean up the stack after 'scanf_s' and 'printf_s'
Note that you could optimize this code further by deferring the stack cleanup after the initial call to printf_s
. You can just wait and do the cleanup later at the end of the function. Functionally, this is equivalent, but an optimizing compiler will often choose to defer stack cleanup to produce more efficient code because it can interleave it within other time-consuming instructions.
Also note that you technically do not need the DWORD PTR
directives that I've used in the code because the inline assembler (and MASM syntax in general) tries to read your mind and assemble the code that you meant to write. However, I like to write it to be explicit. It just means that the value you're loading is DWORD
-sized (32 bits). All pointers on 32-bit x86 are 32 bits in size, as are int
values and single-precision float
values. QWORD
means 64 bits, like an __int64
value or a double
value.
Warning: When I first tested this code in MSVC using inline assembly, I couldn't get it to run. It worked fine when assembled separately using MASM, but when written using inline assembly, I couldn't execute it without getting an "access violation" error. In fact, when I tried your original code, I got the same error. Initially, I couldn't figure out how you were able to get your code to run, either!
Finally, I was able to diagnose the problem: by default, my MSVC project had been configured to dynamically link to the C runtime (CRT) library. The implementation of printf
/printf_s
is apparently doing some type of debug check that is causing this code to fail. I still am not entirely sure what the purpose of this validation code is, or exactly how it works, but it appears to be checking a certain offset within the stack for a sentinel value. Anyway, when I switched to statically linking to the CRT, everything runs as expected.
At least, when I compiled the code as a "release" build. In a "debug" build, the compiler can't tell that you need floating-point support (since all the floating-point stuff is in the inline assembly, which it can't parse), so it fails to tell the linker to link in the floating-point support from the CRT. As a result, the application bombs as soon as you run it and try to use scanf_s
with a floating-point value. There are various ways of fixing this, but the simplest way would be to simply explicitly initialize the afloat
value (it doesn't matter what you initialize it to; 0.0
or any other value would work just fine).
I suppose you are statically linking the CRT and running a release build, which is why your original code was executing. In that case, the code I've shown will both execute and return the correct results. However, if you're trying to learn assembly language, I strongly recommend avoiding inline assembly and writing functions directly in MASM. This is supported from within the Visual Studio environment, too; for setup instructions, see here (same for all versions).
Upvotes: 3
Reputation: 364180
Have you used a debugger to look at registers / memory?
fstp [esp]
is extremely suspicious, because ST0 should be empty at that point. scanf returns an integer, and the calling convention requires the x87 stack to be empty on call/return, except for FP return values.
I forget what happens when you FST while the x87 stack is empty. If you get zero, that would explain it, since this is what you're passing to printf.
add esp, 8
/ sub esp, 8
is completely redundant. There's no point in doing that. You can just take it out. (Or comment it out but leave it there as a reminder that you've optimized by reusing the arg space on the stack instead of popping it and pushing new stuff.)
Since scanf
writes the double
at the address you passed, you could avoid copying it by getting it to write it near the bottom of the stack, and then adjust ESP to right below it. Push a format string and you're ready to call printf, with the double
already where it needs to be on the stack as the second arg.
sub esp, 8 ; reserve 8 bytes for storing a `double` on the stack
push esp ; push a pointer to the bottom of that 16B we just reserved
push OFFSET input_format ; (only works if it's in static storage, but it's a string constant so you should have done that instead of initializing a non-const array with a literal inside `main`.)
call scanf ; scanf("%lf", ESP_before_we_pushed_args)
add esp, 8
; TODO: check return value and handle error
; esp now points at the double stored by scanf
push OFFSET output_format
call printf
add esp, 12 ; "pop" the args: format string and the double we made space for before the scanf call.
If the calling convention / ABI you're using requires that you make function calls with ESP 16B-aligned, you can't shorten it quite this much: you'll need an lea eax, [esp+4]
/ push eax
or something instead of push esp
, because the double
can't be right above scanf's second arg but also be printf's second arg if both calls have the stack 16B-aligned. So have scanf store the double
at a higher address, and add esp, 12
to reach it.
IDK if MSVC-style inline-asm guarantees any kind of stack alignment in the first place. Inline-asm seems to make this problem more complicated in some ways than just writing the whole function in asm.
Upvotes: 1