在 Microsoft 程序集中读取和显示浮点数或双精度数 Visual Studio
Read and display float or double number in assembly in Microsoft Visual Studio
我正在使用 Microsoft Visual Studio 2015 来学习内联汇编编程。我已经阅读了很多关于 Whosebug 的帖子,包括最相关的 this,但是在我尝试了这些方法之后,结果仍然是 0.0000。我首先使用 float 并将值存储到 fpu 但 reuslt 是相同的并尝试将值传递给 eax 但仍然没有帮助。
这是我的代码:
#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;
}
结果:
您是否使用调试器查看寄存器/内存?
fstp [esp]
非常可疑,因为此时 ST0 应该为空。 scanf returns 一个整数,调用约定要求 x87 堆栈在 call/return 上为空,FP return 值除外。
我忘记了在 x87 堆栈为空时执行 FST 时会发生什么。如果你得到零,那就可以解释了,因为这是你传递给 printf 的内容。
add esp, 8
/ sub esp, 8
完全多余。这样做没有意义。你可以把它拿出来。 (或者将其注释掉但保留在那里以提醒您已经通过在堆栈上重用 arg space 而不是弹出它并推送新内容来进行优化。)
由于 scanf
将 double
写入您传递的地址,您可以通过将其写入堆栈底部附近来避免复制它,然后将 ESP 调整到它的正下方。推送一个格式字符串,您就可以调用 printf,double
已经作为第二个参数出现在堆栈上。
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.
如果您使用的调用约定/ABI 要求您使用 ESP 16B 对齐进行函数调用,则不能将其缩短太多:您需要一个 lea eax, [esp+4]
/ push eax
或其他东西而不是 push esp
,因为 double
不能正好在 scanf 的第二个参数之上,而且 be printf 的第二个参数如果两个调用都有堆栈 16B 对齐。所以让 scanf 将 double
存储在更高的地址,并 add esp, 12
到达它。
IDK 如果 MSVC 样式的内联汇编首先保证任何类型的堆栈对齐。内联 asm 似乎使这个问题在某些方面比仅仅在 asm 中编写整个函数更复杂。
您试图打印错误的值。事实上,代码 应该 只是导致将无意义的内容打印到终端。您很幸运看到 0.0
。让我们具体看一下检索浮点值的代码部分,也就是您对scanf_s
:
的调用
lea eax, input_format
push eax
lea eax, afloat
push eax
call scanf_s
add esp, 8
sub esp, 8
fstp [esp]
首先,我没有看到将 8 添加到您的堆栈指针 (esp
) 然后立即从堆栈指针中减去 8 的任何逻辑。连续执行这两个操作只会相互抵消。因此,这两条指令可以删除。
其次,您以错误的顺序将参数压入堆栈。 cdecl 调用约定(由 CRT 函数使用,包括 printf_s
和 scanf_s
)以 逆序 从右到左传递参数。因此,要调用 scanf_s
,首先要将存储输入的浮点值的地址压入,然后将格式控制字符串缓冲区的地址压入。您的代码是错误的,因为它将参数从左推到右。你很幸运 printf_s
,因为你只传递了一个参数,但是因为你传递了两个参数给 scanf_s
,坏事发生了。
第三个问题是您似乎假设 scanf_s
return 直接输出。
如果是这样,并且您请求了一个浮点值,那么您认为 cdecl
调用约定会将它 return 放在浮点顶部的那个值是正确的堆栈,在 FP(0)
中,因此您将正确地弹出该值并将其存储在内存位置 (fstp [esp]
)。
虽然scanf_s
return一个值(一个整数值,表示成功转换和分配的字段数),但它不return 来自标准输入流的值。事实上,它无法做到这一点,因为它支持任意类型的输入。这就是为什么它使用指向内存位置的指针来存储值的原因。您可能已经知道这一点,因为您安排将该指针作为参数传递给函数。
现在,为什么在最后调用 printf
时得到 0.0
的输出?因为 fstp [esp]
指令。这会将浮点堆栈的顶部值弹出,并将其存储在 esp
中包含的内存地址中。我已经指出 scanf_s
不会在浮点堆栈上放置任何值,因此从技术上讲,它包含 meaningless/garbage 数据。但在你的情况下,你很幸运 FP(0)
实际上包含 0.0
,所以这就是打印的内容。为什么 FP(0)
包含 0.0
?我猜是因为这是您正在 运行 调试的调试版本,并且 CRT 正在将堆栈归零。或者可能是因为当堆栈为空时 FSTP
从堆栈中弹出。我不知道,我也没有在任何地方看到这个记录。但是当你写错代码时会发生什么并不重要,因为你应该努力只写 正确 代码!
下面是正确的代码:
; 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'
请注意,您可以通过在初始调用 printf_s
后推迟堆栈清理来进一步优化此代码。您可以等待,稍后在函数结束时进行清理。从功能上讲,这是等效的,但优化编译器通常会选择推迟堆栈清理以生成更高效的代码,因为它可以将其交织在其他耗时的指令中。
另请注意,从技术上讲,您不需要我在代码中使用的 DWORD PTR
指令,因为内联 assembler(以及一般的 MASM 语法)会尝试读懂您的想法并assemble 您打算编写的代码。但是,我喜欢将其写得明确。这只是意味着您要加载的值是 DWORD
大小(32 位)。 32 位 x86 上的所有指针都是 32 位大小,int
值和单精度 float
值也是如此。 QWORD
表示 64 位,如 __int64
值或 double
值。
警告: 当我第一次使用内联汇编在 MSVC 中测试此代码时,我无法将其转换为 运行。当 assembled 单独使用 MASM 时它工作正常,但是当使用内联汇编编写时,我无法执行它而不会出现 "access violation" 错误。事实上,当我尝试你的原始代码时,我得到了同样的错误。最初,我也不知道您是如何将代码发送到 运行!
最后,我能够诊断出问题:默认情况下,我的 MSVC 项目已配置为 动态 link 到 C 运行 时间(CRT) 库。 printf
/printf_s
的实现显然是在进行某种类型的调试检查,导致此代码失败。我仍然不完全确定此验证代码的目的是什么,或者它究竟是如何工作的,但它似乎正在检查堆栈中的某个偏移量以获取标记值。无论如何,当我切换到静态 linking到CRT时,一切运行都如预期的那样。
至少,当我将代码编译为 "release" 构建时。在 "debug" 构建中,编译器无法告诉您需要浮点支持(因为所有浮点内容都在内联汇编中,它无法解析),所以它无法告诉在 CRT 的浮点支持中 link 改为 link。因此,应用程序会在您 运行 并尝试将 scanf_s
与浮点值一起使用时立即崩溃。有多种方法可以修复此问题,但最简单的方法是简单地显式初始化 afloat
值(将其初始化为什么并不重要;0.0
或任何其他值都可以正常工作).
我想您正在静态 linking CRT 和 运行ning 发布版本,这就是您的原始代码正在执行的原因。在那种情况下,我展示的代码将执行并 return 正确的结果。但是,如果您正在尝试学习汇编语言,我强烈建议避免内联汇编并直接在 MASM 中编写函数。 Visual Studio 环境也支持这一点;有关设置说明,请参阅 here(所有版本均相同)。
感谢您提供的所有帮助和注意事项。还有一些问题我需要配置和修复,但在阅读了 this website 上的大多数浮点运算符后,我终于想出了一个解决方案来读取和显示 Microsoft Visual Studio 上的浮点数。对于我在此 post 中使用的所有不良约定和缺乏评论,我深表歉意,我将学习以更好的风格进行编程。再次感谢!
这是我的工作代码(与原始 post 不同,因为我想在那里使用浮点数):
#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;
}
结果如下所示:
我正在使用 Microsoft Visual Studio 2015 来学习内联汇编编程。我已经阅读了很多关于 Whosebug 的帖子,包括最相关的 this,但是在我尝试了这些方法之后,结果仍然是 0.0000。我首先使用 float 并将值存储到 fpu 但 reuslt 是相同的并尝试将值传递给 eax 但仍然没有帮助。
这是我的代码:
#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;
}
结果:
您是否使用调试器查看寄存器/内存?
fstp [esp]
非常可疑,因为此时 ST0 应该为空。 scanf returns 一个整数,调用约定要求 x87 堆栈在 call/return 上为空,FP return 值除外。
我忘记了在 x87 堆栈为空时执行 FST 时会发生什么。如果你得到零,那就可以解释了,因为这是你传递给 printf 的内容。
add esp, 8
/ sub esp, 8
完全多余。这样做没有意义。你可以把它拿出来。 (或者将其注释掉但保留在那里以提醒您已经通过在堆栈上重用 arg space 而不是弹出它并推送新内容来进行优化。)
由于 scanf
将 double
写入您传递的地址,您可以通过将其写入堆栈底部附近来避免复制它,然后将 ESP 调整到它的正下方。推送一个格式字符串,您就可以调用 printf,double
已经作为第二个参数出现在堆栈上。
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.
如果您使用的调用约定/ABI 要求您使用 ESP 16B 对齐进行函数调用,则不能将其缩短太多:您需要一个 lea eax, [esp+4]
/ push eax
或其他东西而不是 push esp
,因为 double
不能正好在 scanf 的第二个参数之上,而且 be printf 的第二个参数如果两个调用都有堆栈 16B 对齐。所以让 scanf 将 double
存储在更高的地址,并 add esp, 12
到达它。
IDK 如果 MSVC 样式的内联汇编首先保证任何类型的堆栈对齐。内联 asm 似乎使这个问题在某些方面比仅仅在 asm 中编写整个函数更复杂。
您试图打印错误的值。事实上,代码 应该 只是导致将无意义的内容打印到终端。您很幸运看到 0.0
。让我们具体看一下检索浮点值的代码部分,也就是您对scanf_s
:
lea eax, input_format
push eax
lea eax, afloat
push eax
call scanf_s
add esp, 8
sub esp, 8
fstp [esp]
首先,我没有看到将 8 添加到您的堆栈指针 (esp
) 然后立即从堆栈指针中减去 8 的任何逻辑。连续执行这两个操作只会相互抵消。因此,这两条指令可以删除。
其次,您以错误的顺序将参数压入堆栈。 cdecl 调用约定(由 CRT 函数使用,包括 printf_s
和 scanf_s
)以 逆序 从右到左传递参数。因此,要调用 scanf_s
,首先要将存储输入的浮点值的地址压入,然后将格式控制字符串缓冲区的地址压入。您的代码是错误的,因为它将参数从左推到右。你很幸运 printf_s
,因为你只传递了一个参数,但是因为你传递了两个参数给 scanf_s
,坏事发生了。
第三个问题是您似乎假设 scanf_s
return 直接输出。
如果是这样,并且您请求了一个浮点值,那么您认为 cdecl
调用约定会将它 return 放在浮点顶部的那个值是正确的堆栈,在 FP(0)
中,因此您将正确地弹出该值并将其存储在内存位置 (fstp [esp]
)。
虽然scanf_s
return一个值(一个整数值,表示成功转换和分配的字段数),但它不return 来自标准输入流的值。事实上,它无法做到这一点,因为它支持任意类型的输入。这就是为什么它使用指向内存位置的指针来存储值的原因。您可能已经知道这一点,因为您安排将该指针作为参数传递给函数。
现在,为什么在最后调用 printf
时得到 0.0
的输出?因为 fstp [esp]
指令。这会将浮点堆栈的顶部值弹出,并将其存储在 esp
中包含的内存地址中。我已经指出 scanf_s
不会在浮点堆栈上放置任何值,因此从技术上讲,它包含 meaningless/garbage 数据。但在你的情况下,你很幸运 FP(0)
实际上包含 0.0
,所以这就是打印的内容。为什么 FP(0)
包含 0.0
?我猜是因为这是您正在 运行 调试的调试版本,并且 CRT 正在将堆栈归零。或者可能是因为当堆栈为空时 FSTP
从堆栈中弹出。我不知道,我也没有在任何地方看到这个记录。但是当你写错代码时会发生什么并不重要,因为你应该努力只写 正确 代码!
下面是正确的代码:
; 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'
请注意,您可以通过在初始调用 printf_s
后推迟堆栈清理来进一步优化此代码。您可以等待,稍后在函数结束时进行清理。从功能上讲,这是等效的,但优化编译器通常会选择推迟堆栈清理以生成更高效的代码,因为它可以将其交织在其他耗时的指令中。
另请注意,从技术上讲,您不需要我在代码中使用的 DWORD PTR
指令,因为内联 assembler(以及一般的 MASM 语法)会尝试读懂您的想法并assemble 您打算编写的代码。但是,我喜欢将其写得明确。这只是意味着您要加载的值是 DWORD
大小(32 位)。 32 位 x86 上的所有指针都是 32 位大小,int
值和单精度 float
值也是如此。 QWORD
表示 64 位,如 __int64
值或 double
值。
警告: 当我第一次使用内联汇编在 MSVC 中测试此代码时,我无法将其转换为 运行。当 assembled 单独使用 MASM 时它工作正常,但是当使用内联汇编编写时,我无法执行它而不会出现 "access violation" 错误。事实上,当我尝试你的原始代码时,我得到了同样的错误。最初,我也不知道您是如何将代码发送到 运行!
最后,我能够诊断出问题:默认情况下,我的 MSVC 项目已配置为 动态 link 到 C 运行 时间(CRT) 库。 printf
/printf_s
的实现显然是在进行某种类型的调试检查,导致此代码失败。我仍然不完全确定此验证代码的目的是什么,或者它究竟是如何工作的,但它似乎正在检查堆栈中的某个偏移量以获取标记值。无论如何,当我切换到静态 linking到CRT时,一切运行都如预期的那样。
至少,当我将代码编译为 "release" 构建时。在 "debug" 构建中,编译器无法告诉您需要浮点支持(因为所有浮点内容都在内联汇编中,它无法解析),所以它无法告诉在 CRT 的浮点支持中 link 改为 link。因此,应用程序会在您 运行 并尝试将 scanf_s
与浮点值一起使用时立即崩溃。有多种方法可以修复此问题,但最简单的方法是简单地显式初始化 afloat
值(将其初始化为什么并不重要;0.0
或任何其他值都可以正常工作).
我想您正在静态 linking CRT 和 运行ning 发布版本,这就是您的原始代码正在执行的原因。在那种情况下,我展示的代码将执行并 return 正确的结果。但是,如果您正在尝试学习汇编语言,我强烈建议避免内联汇编并直接在 MASM 中编写函数。 Visual Studio 环境也支持这一点;有关设置说明,请参阅 here(所有版本均相同)。
感谢您提供的所有帮助和注意事项。还有一些问题我需要配置和修复,但在阅读了 this website 上的大多数浮点运算符后,我终于想出了一个解决方案来读取和显示 Microsoft Visual Studio 上的浮点数。对于我在此 post 中使用的所有不良约定和缺乏评论,我深表歉意,我将学习以更好的风格进行编程。再次感谢!
这是我的工作代码(与原始 post 不同,因为我想在那里使用浮点数):
#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;
}
结果如下所示: