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

我在C中有一个使用NASM函数的程序。这是C程序的代码:


#include <stdio.h>

#include <string.h>

#include <math.h>


extern float hyp(float a); // supposed to calculate 1/(2 - a) + 6


void 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.0


section .text

global hyp

hyp:

    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 clean

main.o: main.c

    gcc -c main.c -o main.o -m32 -std=c99

hyp.o: hyp.asm

    nasm -f elf32 -o hyp.o hyp.asm -D UNIX

project: main.o hyp.o

    gcc -o project main.o hyp.o -m32 -lm

clean:

    rm -rf *.o

程序运行时,将输出以下内容:


5.767442 5.545455 -4.000010

最后一个数字是正确的。我的问题是:即使输入相同,为什么这些结果也不同?


呼如林
浏览 247回答 1
1回答

Qyouu

该错误是您执行此操作:fstp dword[b]这会覆盖的值b,因此,下次调用该函数时,常量是错误的。在整个程序的输出中,这显示为最右边的评估是唯一正确的评估,因为编译器printf从右到左评估了参数。(可以按任意顺序对多参数函数的参数求值。)您应该使用该.rodata部分作为常量。那么程序将崩溃而不是覆盖常量。您可以避免使用fdivr而不是来存储和重新加载中间值fdiv。hyp:&nbsp; &nbsp; fld&nbsp; &nbsp; &nbsp;DWORD PTR [b]&nbsp; &nbsp; fsub&nbsp; &nbsp; DWORD PTR [esp+4]&nbsp; &nbsp; fdivr&nbsp; &nbsp;DWORD PTR [a]&nbsp; &nbsp; fadd&nbsp; &nbsp; DWORD PTR [c]&nbsp; &nbsp; ret或者,执行Forth程序员会做的事情,并在其他所有内容之前加载常量1,因此在需要时将其保存在ST(1)中。这使您可以使用fld1而不是将1.0放入内存中。hyp:&nbsp; &nbsp; fld1&nbsp; &nbsp; fld&nbsp; &nbsp; &nbsp;DWORD PTR [b]&nbsp; &nbsp; fsub&nbsp; &nbsp; DWORD PTR [esp+4]&nbsp; &nbsp; fdivp&nbsp; &nbsp; fadd&nbsp; &nbsp; DWORD PTR [c]&nbsp; &nbsp; ret您不需要发出finit,因为ABI保证在流程启动期间已经完成了此操作。您不需要为此函数设置EBP,因为它不会调用任何函数(该术语的术语是“叶子过程”),也不需要堆栈上的任何暂存空间。如果您使用的是现代CPU,另一种选择是使用较新的SSE2指令。这将为您提供普通的寄存器,而不是操作数堆栈,并且还意味着计算实际上是全部完成,float而不是80位扩展,这可能非常重要-一些复杂的数值算法,如果它们的浮点精度高于整数,则会出错。设计师期望的。但是,由于您使用的是32位ELF ABI,因此返回值仍需要在ST(0)中结束,并且SSE和x87寄存器之间没有直接移动指令,因此必须遍历内存。抱歉,我不知道如何用Intel语法编写SSE2指令。hyp:&nbsp; &nbsp; subl&nbsp; &nbsp; $4, %esp&nbsp; &nbsp; movss&nbsp; &nbsp;b, %xmm1&nbsp; &nbsp; subss&nbsp; &nbsp;8(%esp), %xmm1&nbsp; &nbsp; movss&nbsp; &nbsp;a, %xmm0&nbsp; &nbsp; divss&nbsp; &nbsp;%xmm1, %xmm0&nbsp; &nbsp; addss&nbsp; &nbsp;c, %xmm0&nbsp; &nbsp; movss&nbsp; &nbsp;%xmm0, (%esp)&nbsp; &nbsp; flds&nbsp; &nbsp; (%esp)&nbsp; &nbsp; addl&nbsp; &nbsp; $4, %esp&nbsp; &nbsp; ret在64位ELF ABI中,在XMM0中有浮点返回值(默认情况下也将参数传递到寄存器中),这就是hyp:&nbsp; &nbsp; movss&nbsp; &nbsp;b(%rip), %xmm1&nbsp; &nbsp; subss&nbsp; &nbsp;%xmm0, %xmm1&nbsp; &nbsp; movss&nbsp; &nbsp;a(%rip), %xmm0&nbsp; &nbsp; divss&nbsp; &nbsp;%xmm1, %xmm0&nbsp; &nbsp; addss&nbsp; &nbsp;c(%rip), %xmm0&nbsp; &nbsp; ret
打开App,查看更多内容
随时随地看视频慕课网APP