为了账号安全,请及时绑定邮箱和手机立即绑定

C-外部组装函数使用相同的输入返回不同的结果

C-外部组装函数使用相同的输入返回不同的结果

呼如林 2021-04-09 17:14:38
我在C中有一个使用NASM函数的程序。这是C程序的代码:#include <stdio.h>#include <string.h>#include <math.h>extern float hyp(float a); // supposed to calculate 1/(2 - a) + 6void test(float (*f)(float)){    printf("%f %f %f\n", f(2.1), f(2.1), f(2.1));}void main(int argc, char** argv){    for(int i = 1; i < argc; i++){        if(!strcmp(argv[i], "calculate")){            test(hyp);        }    }}这是NASM函数:section .data    a dd 1.0    b dd 2.0    c dd 6.0section .textglobal hyphyp:    push ebp    mov ebp, esp    finit    fld dword[b]    fsub dword[ebp + 8]    fstp dword[b]    fld dword[a]    fdiv dword[b]    fadd dword[c]    mov esp, ebp    pop ebp    ret这些程序在Linux中与gcc和nasm链接在一起。这是Makefile:all: project cleanmain.o: main.c    gcc -c main.c -o main.o -m32 -std=c99hyp.o: hyp.asm    nasm -f elf32 -o hyp.o hyp.asm -D UNIXproject: main.o hyp.o    gcc -o project main.o hyp.o -m32 -lmclean:    rm -rf *.o程序运行时,将输出以下内容:5.767442 5.545455 -4.000010最后一个数字是正确的。我的问题是:即使输入相同,为什么这些结果也不同?
查看完整描述

1 回答

?
Qyouu

TA贡献1786条经验 获得超11个赞

该错误是您执行此操作:


fstp dword[b]

这会覆盖的值b,因此,下次调用该函数时,常量是错误的。在整个程序的输出中,这显示为最右边的评估是唯一正确的评估,因为编译器printf从右到左评估了参数。(可以按任意顺序对多参数函数的参数求值。)


您应该使用该.rodata部分作为常量。那么程序将崩溃而不是覆盖常量。


您可以避免使用fdivr而不是来存储和重新加载中间值fdiv。


hyp:

    fld     DWORD PTR [b]

    fsub    DWORD PTR [esp+4]

    fdivr   DWORD PTR [a]

    fadd    DWORD PTR [c]

    ret

或者,执行Forth程序员会做的事情,并在其他所有内容之前加载常量1,因此在需要时将其保存在ST(1)中。这使您可以使用fld1而不是将1.0放入内存中。


hyp:

    fld1

    fld     DWORD PTR [b]

    fsub    DWORD PTR [esp+4]

    fdivp

    fadd    DWORD PTR [c]

    ret

您不需要发出finit,因为ABI保证在流程启动期间已经完成了此操作。您不需要为此函数设置EBP,因为它不会调用任何函数(该术语的术语是“叶子过程”),也不需要堆栈上的任何暂存空间。


如果您使用的是现代CPU,另一种选择是使用较新的SSE2指令。这将为您提供普通的寄存器,而不是操作数堆栈,并且还意味着计算实际上是全部完成,float而不是80位扩展,这可能非常重要-一些复杂的数值算法,如果它们的浮点精度高于整数,则会出错。设计师期望的。但是,由于您使用的是32位ELF ABI,因此返回值仍需要在ST(0)中结束,并且SSE和x87寄存器之间没有直接移动指令,因此必须遍历内存。抱歉,我不知道如何用Intel语法编写SSE2指令。


hyp:

    subl    $4, %esp

    movss   b, %xmm1

    subss   8(%esp), %xmm1

    movss   a, %xmm0

    divss   %xmm1, %xmm0

    addss   c, %xmm0

    movss   %xmm0, (%esp)

    flds    (%esp)

    addl    $4, %esp

    ret

在64位ELF ABI中,在XMM0中有浮点返回值(默认情况下也将参数传递到寄存器中),这就是


hyp:

    movss   b(%rip), %xmm1

    subss   %xmm0, %xmm1

    movss   a(%rip), %xmm0

    divss   %xmm1, %xmm0

    addss   c(%rip), %xmm0

    ret



查看完整回答
反对 回复 2021-04-16
  • 1 回答
  • 0 关注
  • 246 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信