c - C - 外部汇编函数使用相同的输入返回不同的结果
问题描述
我有一个使用 NASM 函数的 C 语言程序。这是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 链接。这是生成文件:
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
最后一个数字是正确的。我的问题是:为什么即使输入相同,这些结果也会有所不同?
解决方案
错误是你这样做:
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
您不需要发出 a 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
推荐阅读
- mysql - 如何使用join和left join mysql
- php - 使用 Joins 在 laravel 视图中显示 Json 数据
- typescript - 如何让 TypeScript 正确地对泛型函数进行类型检查调用,然后再调用具有泛型类型参数的函数?
- php - PHP - 在选择框中设置默认年份
- javascript - 仅在 Loader 使用平面列表在 React Native 中结束后才显示空列表消息
- c++ - 如何在编译时使用 std::make_tuple?
- angular5 - 为什么我的 angular5.2 动画不起作用
- python - Python-DOCX 并从 word 文档中提取编号项目
- azure - 如何在 azure 上部署我现有的 webapp
- javascript - 在d3中获取大部分点集中在2D图表上的中心