汇编中的 Scanf 和 printf 函数,关于 char* 和 double 的示例

Scanf and printf functions in assembly, example on char* and double

我有一个任务要做,使用 char* 和 double 变量调用 scanf 和 printf 函数。 Char* 正在工作,但我对 double 有疑问。

函数:用于 scanf/printf char*,函数 1:用于 scanf/printf 双精度。比如我编译后的结果:

(scanf)b

(printf)字符: b

(scanf)1.3

(printf)双: 99997200381866062965879955188785948733402760577162787362451212786.000000

问题似乎出在双变量的 printf 上,但我不知道如何解决。

.data
STDIN = 0
STDOUT = 1
SYSREAD = 3
SYSWRITE = 4
SYSEXIT = 1
EXIT_SUCCESS = 0

format_inchar: .string "%c"
format_indouble: .string "%lf"
char: .ascii " "
double: .double 0.0

format_string1: .string "char: %c\n"   
format_double1: .string "double: %f\n"
.text
.global main
main:

function:

    push $char
    push $format_inchar
    call scanf

    push char
    push $format_string1
    call printf

function1:

    push $double
    push $format_indouble
    call scanf

    push double
    push $format_double1
    call printf

exit:
movl $SYSEXIT, %eax
movl $EXIT_SUCCESS, %ebx
int [=10=]x80

32 位代码无法通过一条指令从内存中 push 64 位 double。 64 位代码不会在堆栈上传递参数,因此我假设这是 32 位代码。 (也来自使用 int 0x80。)

push double在32位代码中是pushl压低4字节printf 从堆栈上它上面的任何内容中获取高 4 字节(包含指数和尾数的 most-significant 位)。在那种情况下,假设 scanf 没有破坏其堆栈参数,您的 double bit-pattern 的高 4 字节来自地址 $format_indouble.

你可能想要 pushl double+4 ; pushl double 将 8 字节 double 分成两半。

(operand-size 后缀在这里是可选的,但我建议它避免让您产生歧义。)

或者您可以使用 SSE2 movsd 将 8 个字节复制到堆栈。

    sub    , %esp
    movsd  double, %xmm0
    movsd  %xmm0, (%esp)

push 比较特殊,可以从内存复制到内存。具有显式源和目标的指令不能这样做。)

当然你的输入不需要静态存储space;您可以将指向堆栈内存的指针传递给 scanf。然后你的 double 已经在堆栈上了,你可以只存储一个指向它下面的格式字符串的指针。


顺便说一句,调用 scanfprintf 时,堆栈没有按 16 对齐。 ABI 确实需要这个,所以你很幸运,你的 glibc 构建恰好没有在这些函数的堆栈内存上使用 movaps,这会导致段错误。

在调用main之前,堆栈按16对齐。call压入一个return地址。因此,还有 3 个推 re-aligns 那个堆栈。函数条目上的一个虚拟推送将为您设置具有 8 个字节 args 的函数。

通常你会 add , %esp 在调用 return 之后从堆栈中清除参数。


变量名: double 是 C 中的类型名称(和关键字)。这使得它对于调用 C 函数的汇编程序来说是一个奇怪的名称。 dbl 之类的内容可能看起来更好。