浮点类型的返回值比较特殊,它不像整数或字符串类型的返回值,可以存储在EAX寄存器里,对于C风格的汇编函数,浮点类型的返回值需要存储在ST(0)的浮点寄存器里。调用这类函数的程式,需要负责将返回值从ST(0)里提取出来...

    本文由zengl.com站长对
    http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。

    另外再附加一个英特尔英文手册的共享链接地址:
    http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)

    本篇翻译对应汇编教程英文原著的第431页到第439页,对应原著第14章 (注意这里的页数不是页脚的页数,而是pdf电子档顶部,分页输入框中的页数,也就是包含了目录,前言部分的总页数,pdf电子档总页数是577,当前已翻译完的页数为439 / 577)。

    上一篇文章里介绍了整数类型和字符串类型的返回值,本篇文章继续介绍其他类型的返回值。

Using floating-point return values 使用浮点类型的返回值:

    浮点类型的返回值比较特殊,它不像整数或字符串类型的返回值,可以存储在EAX寄存器里,对于C风格的汇编函数,浮点类型的返回值需要存储在ST(0)的浮点寄存器里。调用这类函数的程式,需要负责将返回值从ST(0)里提取出来。

    之前的章节提到过,FPU浮点寄存器里是以双精度扩展的格式来存储浮点数的,不过,当C程式需要从浮点寄存器里获取浮点数时,FPU会自动将双精度扩展格式转为所需的浮点格式,在C和C++程式里会使用到两种类型的浮点格式:
  • float: Represents a single-precision floating-point value
    float类型:表示单精度浮点数
  • double: Represents a double-precision floating-point value
    double类型:表示双精度浮点数
    这两种浮点格式在之前的"汇编数据处理 (三) 浮点数"章节里详细的介绍过,这两种浮点数在存储到FPU寄存器时,会自动转为双精度扩展格式,反过来,当从FPU寄存器获取结果时,双精度扩展格式又可以自动转为单精度或双精度格式。

    当C程式里需要使用返回单精度浮点数的汇编函数时,必须在使用该函数之前,先声明该函数的原型,例如:

float function1(float, float, int);

    上面例子,声明了一个名为function1的汇编函数,该函数需要接受三个输入参数(前两个参数为float单精度浮点格式,最后一个参数为int整数类型),汇编函数的返回值会自动从双精度扩展格式转为float(单精度浮点格式),如果C程式里需要使用double类型的双精度浮点数的话,则必须使用如下声明:

double function1(double, int);

    上面将汇编函数的返回值声明为double(双精度浮点格式),这样在获取汇编函数的ST(0)里的返回值时,就会自动将ST(0)里的双精度扩展格式转为双精度格式。

    下面的areafunc.s程式定义了一个会返回浮点数的汇编函数:

# areafunc.s - An example of a floating point return value
.section .text
.type areafunc, @function
.globl areafunc
    areafunc:
    pushl %ebp
    movl %esp, %ebp
    fldpi
    filds 8(%ebp)
    fmul %st(0), %st(0)
    fmul %st(1), %st(0)
    movl %ebp, %esp
    popl %ebp
    ret
 

    上面的代码里,先通过fldpi指令将3.14159...的pi值压入FPU寄存器栈,然后通过filds 8(%ebp)指令将栈里的半径参数值压入寄存器栈,此时,ST(0)里存储的是半径值,之前压入寄存器栈的pi值则自动移入ST(1),随后的fmul %st(0), %st(0)指令将ST(0)里的半径值开平方,最后通过fmul %st(1), %st(0)指令将半径的平方值与pi值相乘,得到的圆面积存储在ST(0)里作为汇编函数的浮点数结果返回。

    下面的floattest.c程式就用到了上面的汇编函数来计算圆面积:

/* floattest.c - An example of using floating point return values */
#include <stdio.h>

float areafunc(int);

int main()
{
	int radius = 10;
	float result;
	result = areafunc(radius);
	printf("The result is %f\n", result);

	result = areafunc(2);
	printf("The result is %f\n", result);
	return 0;
}


    上面程序编译运行的情况如下:

$ as -o areafunc.o areafunc.s
$ gcc -o floattest floattest.c areafunc.o
$ ./floattest

The result is 314.159271
The result is 12.566371

$

Using multiple input values 使用多个输入参数:

    上面的floattest例子里,汇编函数只接受一个输入参数,实际上,你可以根据需要给汇编函数传递任意数目的输入参数,这些参数都会存储在栈里,不过当使用多个参数时,需要注意参数的传递顺序,例如下面的代码片段:

int i = 10;
int j = 20;
result = function1(i, j);

    上面代码中,在调用function1函数时,参数在栈里的存储情况会如下图所示:


图1

    可以看到第二个参数会被先压入栈,而第一个参数后压入栈,也就是说第一个参数会和EBP最近,越后面的参数,距离EBP越远,例如,第一个参数可以使用8(%ebp)来表示(即EBP加8的偏移),第二个参数则可以使用12(%ebp)来表示(即EBP加12的偏移)。

    为了完整的演示多个输入参数的用法,下面的greater.s先定义了一个会接受两个输入参数的汇编函数:

# greater.s - An example of using multiple input values
.section .text
.globl greater
greater:
    pushl %ebp
    movl %esp, %ebp
    movl 8(%ebp), %eax
    movl 12(%ebp), %ecx
    cmpl %ecx, %eax
    jge end
    movl %ecx, %eax
end:
    movl %ebp, %esp
    popl %ebp
    ret
 

    上面的greater函数会从栈里将第一个输入参数通过movl 8(%ebp), %eax指令设置到EAX寄存器,将第二个输入参数通过movl 12(%ebp), %ecx指令设置到ECX寄存器,然后由cmpl %ecx, %eax指令进行比较操作,如果EAX大于或等于ECX(即第一个参数大于等于第二个参数)则直接用jge end指令跳转到end结束位置,反之,如果ECX大于EAX,则将ECX的值覆盖到EAX里,作为结果返回,从而将两输入参数里较大的值进行返回。

    下面的multtest.c就利用上面的greater汇编函数来对两输入参数,进行大小的比较:

/* multtest.c - An example of using multiple input values */
#include <stdio.h>

int main()
{
	int i = 10;
	int j = 20;
	int k = greater(i, j);
	printf("The larger value is %d\n", k);
	return 0;
}


    上面的multtest.c程式将 i 和 j 两变量的值作为greater函数的参数,进行大小比较,并将该函数返回的较大的值存储到变量k里,最后将k值打印出来,该程式的运行结果如下:

$ as -o greater.o greater.s
$ gcc -o multtest multtest.c greater.o
$ ./multtest

The larger value is 20
$

Using mixed data type input values 混合使用不同类型的输入参数:

    当汇编函数里使用多个不同类型的输入参数时,事情就会变得更加复杂,主要是会出现两个问题:
  • The calling program can place values on the stack in the wrong order.
    C程式在调用汇编函数时,可能会按错误的顺序将参数放入内存栈
  • The assembly function can read values from the stack in the wrong order.
    汇编函数里读取参数时,也可能会按错误的顺序来读取参数的值
    所以,调用函数时以及汇编函数的内部,都必须特别注意栈里参数的顺序,下面就举例来说明这两个可能会出现的问题。

Placing input values in the proper order 调用函数时以正确的顺序放置输入参数:

    当输入参数的尺寸大小不同时(如某些参数是4字节的大小,而另一些参数是8字节的大小等),就很容易会发生调用程式将参数放置到错误的位置的问题,例如,下面的testfunc.s程式定义了一个需要接受两个不同尺寸大小的参数的汇编函数:

# testfunc.s - An example of reading input values wrong
.section .text
.type testfunc, @function
.globl testfunc
testfunc:
    pushl %ebp
    movl %esp, %ebp

    fldl 8(%ebp)
    fimul 16(%ebp)

    movl %ebp, %esp
    popl %ebp
    ret
 

    上面的testfunc函数会假定8(%ebp)即第一个参数为8字节的双精度浮点数,然后通过fldl 8(%ebp)将该参数压入FPU的ST(0)栈顶寄存器,接着会假定16(%ebp)即第二个参数为4字节的整数值,并用fimul 16(%ebp)指令将ST(0)里的浮点数和第二个整数参数相乘,得到的结果存储在ST(0)里作为结果返回。

    因此,在C程式里要想正确调用testfunc汇编函数,就必须将双精度浮点数作为第一个参数,将整数作为第二个参数,如果像下面的badprog.c程式那样,反过来将整数作为第一个参数,将浮点数作为第二个参数的话,程序执行时就会产生错误的结果:

/* badprog.c - An example of passing input values in the wrong order */
#include <stdio.h>

double testfunc(int, double);

int main()
{
	int i = 10;
	double j = 3.14159;
	double result;
	result = testfunc(i, j);
	printf("The bad result is %g\n", result);
	return 0;
}


    上面程式的编译运行结果如下:

$ as -o testfunc.o testfunc.s
$ gcc -o badprog badprog.c testfunc.o
$ ./badprog

The bad result is -9.29127e+235
$

    之所以会产生上述结果,就是因为在调用时参数放置错误导致的,具体原因如下图所示:


图2

    从上图可以看到,C调用程式将整数作为第一个参数对应上图底部的4个字节,浮点数作为第二个参数对应上图顶部的8个字节,而汇编函数里使用fldl 8(%ebp)指令读取第一个参数时,会将第一个参数当作8个字节的浮点数,因而就会将C传递过来的底部4个字节的整数与上面浮点数的下半部的4个字节数据读取出来,这样读出来的浮点数肯定是错的,同理fimul指令相乘的整数也是错误的整数值,所以导致结果就不对了。

    下面是正确的C调用程式:

/* goodprog.c - An example of passing input values in the proper order */
#include <stdio.h>

double testfunc(double, int);

int main()
{
	double data1 = 3.14159;
	int data2 = 10;
	double result;
	result = testfunc(data1, data2);
	printf("The proper result is %f\n", result);
	return 0;
}


    上面的goodprog.c程式将浮点数作为第一个参数,整数作为第二个参数,与汇编函数里实际的使用情况一致,得到的结果如下:

$ gcc -o goodprog goodprog.c testfunc.o
$ ./goodprog

The proper result is 31.415900
$

    这样得到的就是正确的结果了。

Reading input values in the proper order 以正确的顺序读取输入参数:

    除了C调用程式需要以正确的顺序来放置参数外,还必须确保被调用的汇编函数是以相同的顺序来读取输入参数的,下面就给出一个输入参数比较多的例子,下面的fpmathfunc.s程式其实是修改自之前的"高级数学运算 (二) 基础浮点运算"文章里的fpmath1.s程式,fpmath1.s程式是用于计算((43.65 / 22) + (76.34 * 3.1)) / ((12.43 * 6) – (140.2 / 94.21))这一复杂的表达式的,只不过fpmathfunc.s程式将原来的fpmath1.s程式里固定的数值变为了可变的输入参数的形式,这样函数就可以在C程式里被调用,还可以修改输入参数来产生不同的结果:

# fpmathfunc.s - An example of reading multiple input values
.section .text
.type fpmathfunc, @function
.globl fpmathfunc
fpmathfunc:
    pushl %ebp
    movl %esp, %ebp
    flds 8(%ebp)
    fidiv 12(%ebp)
    flds 16(%ebp)
    flds 20(%ebp)
    fmul %st(1), %st(0)
    fadd %st(2), %st(0)
    flds 24(%ebp)
    fimul 28(%ebp)
    flds 32(%ebp)
    flds 36(%ebp)
    fdivrp
    fsubr %st(1), %st(0)
    fdivr %st(2), %st(0)
    movl %ebp, %esp
    popl %ebp
    ret
 

    上面涉及到的指令的具体含义请参考刚才提到的文章里的fpmath1.s程式,下面的mathtest.c程式就调用上面的汇编函数来完成复杂表达式的计算:

/* mathtest.c - An example of using multiple input values */
#include <stdio.h>

float fpmathfunc(float, int, float, float, float, int, float, float);

int main()
{
	float value1 = 43.65;
	int value2 = 22;
	float value3 = 76.34;
	float value4 = 3.1;
	float value5 = 12.43;
	int value6 = 6;
	float value7 = 140.2;
	float value8 = 94.21;
	float result;
	result = fpmathfunc(value1, value2, value3, value4,
			    value5, value6, value7, value8);
	printf("The final result is %f\n", result);
	return 0;
}


    上面程式的执行情况如下:

$ as -o fpmathfunc.o fpmathfunc.s
$ gcc -o mathtest mathtest.c fpmathfunc.o
$ ./mathtest

The final result is 3.264907
$

    由于mathtest.c程式提供了和之前"高级数学运算 (二) 基础浮点运算"文章里的fpmath1.s程式相同的值,所以产生的结果也是3.264907,当然你可以提供不同的参数,来产生不同的结果。

Using Assembly Functions in C++ Programs 在C++里使用汇编函数:

    在C++里使用汇编函数的方式与C程式里使用汇编函数的方式基本相同,只不过,C++程式默认会使用C++风格来调用函数,如果想在C++里调用C风格的函数(例如汇编函数就是C风格的函数),那么就必须先使用extern关键字来进行声明,例如下面这段声明:

extern "C"
{
    int square(int);
    float areafunc(int);
    char *cpuidfunc();
}

    上面的square,areafunc及cpuidfunc三个函数都是C风格的汇编函数,所以这些函数的声明语句都必须包含在extern "C"的关键字里。

    下面是完整的例子:

# square.s - An example of a function that returns an integer value
.type square, @function
.globl square
square:
	pushl %ebp
	movl %esp, %ebp
	movl 8(%ebp), %eax
	imull %eax, %eax
	movl %ebp, %esp
	popl %ebp
	ret


# areafunc.s - An example of a floating point return value
.section .text
.type areafunc, @function
.globl areafunc
    areafunc:
    pushl %ebp
    movl %esp, %ebp
    fldpi
    filds 8(%ebp)
    fmul %st(0), %st(0)
    fmul %st(1), %st(0)
    movl %ebp, %esp
    popl %ebp
    ret


# cpuidfunc.s - An example of returning a string value
.section .bss
	.comm output, 13
.section .text
.type cpuidfunc, @function
.globl cpuidfunc
cpuidfunc:
	pushl %ebp
	movl %esp, %ebp
	pushl %ebx
	movl $0, %eax
	cpuid
	movl $output, %edi
	movl %ebx, (%edi)
	movl %edx, 4(%edi)
	movl %ecx, 8(%edi)
	movl $output, %eax
	popl %ebx
	movl %ebp, %esp
	popl %ebp
	ret


/* externtest.cpp - An example of using assembly language functions with C++ */
#include <iostream>

using namespace std;

extern "C" {
	int square(int);
	float areafunc(int);
	char *cpuidfunc(void);
}

int main()
{
	int radius = 10;
	int radsquare = square(radius);
	cout << "The radius squared is "<< radsquare << endl;
	float result;
	result = areafunc(radius);
	cout << "The area is " << result << endl;
	cout << "The CPUID is " << cpuidfunc() << endl;
	return 0;
}


    上面的square.s,areafunc.s以及cpuidfunc.s三个汇编程式在本篇文章的开头,以及上一篇"调用汇编模块里的函数 (一)"的文章里都讲解过,这里只是为了方便测试,就再在这里写一遍,externtest.cpp程式里先用extern关键字来声明C风格的汇编函数,然后在main主函数里依次调用这几个汇编函数,程序编译执行的情况如下所示:

$ as -o square.o square.s
$ as -o areafunc.o areafunc.s
$ as -o cpuidfunc.o cpuidfunc.s
$ g++ -o externtest externtest.cpp square.o areafunc.o cpuidfunc.o
$ ./externtest

The radius squared is 100
The area is 314.159
The CPUID is GenuineIntel

$

    运行的结果和预期的一致。

    限于篇幅,本章就到这里,下一篇介绍如何创建静态库等相关的内容。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: 调用汇编模块里的函数 (三) 静态库、共享库、本章结束篇

上一篇: 调用汇编模块里的函数 (一)

相关文章

优化汇编指令 (二)

基本数学运算 (二) 减法和乘法运算

高级数学运算 (四) 高级运算结束篇

汇编里使用Linux系统调用 (二)

使用IA-32平台提供的高级功能 (二)

汇编函数的定义和使用 (三) 汇编函数结束篇