汇编中的 Scanf 和 printf 函数,例如 char* 和 double

Posted

技术标签:

【中文标题】汇编中的 Scanf 和 printf 函数,例如 char* 和 double【英文标题】:Scanf and printf functions in assembly, example on char* and double 【发布时间】:2019-10-25 06:58:36 【问题描述】:

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

Function:用于 scanf/printf char*,function1:用于 scanf/printf double。比如我编译后的结果:

(scanf)b

(printf)char: 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 $0x80

【问题讨论】:

【参考方案1】:

32 位代码不能用一条指令从内存中 push 64 位 double。 64 位代码不会在堆栈上传递参数,所以我认为这是 32 位代码。 (也来自使用int 0x80。)

push double 在 32 位代码中是 pushl,它推动低 4 个字节printf 正在从堆栈上方的任何内容中获取高 4 个字节(包含尾数的指数和最高有效位)。在这种情况下,假设scanf 没有破坏它的堆栈参数,您的double 位模式的高4 字节来自地址$format_indouble

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

(操作数大小的后缀在这里是可选的,但我建议使用它以避免让您产生歧义。)

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

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

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

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


顺便说一句,当您调用 scanfprintf 时,您没有将堆栈对齐为 16。 ABI 确实需要这个,所以你很幸运,你的 glibc 构建碰巧没有在这些函数的堆栈内存上使用movaps,这会出现段错误。

在调用main 之前,堆栈按16 对齐。call 推送返回地址。因此,再进行 3 次推送会重新对齐该堆栈。对函数入口的一次虚拟推送将为您设置具有 8 个字节 args 的函数。

通常你会在调用返回后add $8, %esp 清除堆栈中的参数。


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

【讨论】:

你成功了(惊喜),但值得提醒的是,ABI 是由架构 + 平台定义的。您描述了 sysv/386 abi;但某些 windows 变体可能会有所不同。 @mevets:使用 Windows fastcall OP 的 char 版本也无法正常工作。除了 Windows 不使用 32 位 fastcall 用于可变参数函数,因为它是被调用者弹出约定; IIRC i386 Windows printf/scanf 使用 cdecl 调用约定,在这种情况下与 i386 System V 匹配。

以上是关于汇编中的 Scanf 和 printf 函数,例如 char* 和 double的主要内容,如果未能解决你的问题,请参考以下文章

scanf,printf函数细节

c语言——基本语法

关于scanf的用法

c++中scanf和cout有啥区别

printf和scanf

了解汇编、nasm、x86 中的 printf 函数。我不知道为啥这段代码没有打印出任何东西