C语言中函数调用约定
首先定义如下函数 int function(int a, int,b)
我们都知道,机器是无法识别代码的,它只认识0,1。我们只需要通过another_number = function(a,b)就能够条用该函数。而在cpu中,计算机并不知道调用该函数需要传递几个、什么类型的参数。为了完成调用这一过程,计算机提供了“栈”这种数据结构来支持传递参数。
(栈的结构等相关内容不再赘述)
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改栈,使栈恢复原装(初始状态)。
此处面临两个问题:
参数多于1个时,应该按什么顺序把参数压入栈?
函数调用后,由谁来把栈恢复原装?
在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:
- stdcall
- cdecl
- fastcall
- thiscall
- naked call
stdcall(很多时候被称为pascal调用约定)
(Win32 API函数绝大部分都是采用_stdcall调用约定的。WINAPI其实也只是_stdcall的一个别名而已。)
stdcall的调用约定意味着:1. 参数从右向左压入栈;2. 函数自身修改栈;3. 函数名自动加”_”,后面紧跟一个”@”符号,其后紧跟着参数的尺寸(字节数)。
在编译时,这个函数的名字按照如上所述,会被翻译成_function@8
函数调用过程的汇编语言如下:
1 | push b 第二个参数入栈 |
而对于函数自身,则可以翻译为:
1 | push ebp 保存ebp寄存器,该寄存器将用来保存栈的栈顶指针,可以在函数退出时恢复 |
(贴一张栈帧帮助理解)
函数结束后,ret 8表示清理8个字节的栈,函数自己恢复了栈。
cdecl调用约定(C调用约定)
该约定为C语言默认的调用约定:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。命名规则为在函数名前自动加“_”.
被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
1 | int (__cdecl)function(int a,int b) //加不加_cdecl都一样,只是有没有明确指出的区别 |
因为是手动清栈,所以在最后的ret便不会清理,故无字节数。
函数自身汇编语言如下,
1 | push ebp 保存ebp寄存器,该寄存器将用来保存栈的栈顶指针,可以在函数退出时恢复 |
fastcall
改调用与stdcall类似:
- 函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈
- 被调用函数清理栈
- 函数名修改规则同stdcall
thiscall
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:
- 参数从右向左入栈
- 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入栈。
- 对参数个数不定的,调用者清理栈,否则函数自己清理栈
(由于此调用约定仅适用于 C++,因此它没有 C 名称修饰方案。)
vectorcall
vectorcall 继承于fastcall 但对于fastcall中的整数仍然按照fastcall规则传递 而浮点以及向量将通过寄存器传递
nakedcall
编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计。
__pascal
唯一一个从左到右入栈的约定
函数调用约定导致的常见问题
如果定义的约定和使用的约定不一致,则将导致栈被破坏,导致严重问题,下面是两种常见的问题:
- 函数原型声明和函数体定义不一致
- DLL导入函数时声明了不同的函数约定(所以不能口是心非,会出大问题。)
贴一个微软的对此的解释函数调用约定。
再贴一篇关于从汇编语言角度对参数传递的理解的博客 从汇编角度看函数参数传递—汇编函数参数
Java相关
native关键字
native 用来修饰方法,用 native 声明的方法表示告知 JVM 调用,该方法在外部定义,我们可以用任何语言去实现它。 简单地讲,一个native方法就是一个 Java 调用非 Java 代码的接口。
native 语法:
①、修饰方法的位置必须在返回类型之前,和其余的方法控制符前后关系不受限制。
②、不能用 abstract 修饰,也没有方法体,也没有左右大括号。
③、返回值可以是任意类型
System.LoadLibrary
该函数的作用:将指定的模块加载到调用进程的地址空间中。指定的模块可能会导致其他模块被加载。对于其他加载选项,请使用 LoadLibraryEx函数。Ex(extra-额外的)—-总的来说,装载库文件。与之类似的还有System.load
下面对这两个相似的函数做个解析:
- 它们都可以用来装载库文件,不论是JNI库文件还是非JNI库文件。在任何本地方法被调用之前必须先用这个两个方法之一把相应的JNI库文件装载。
- System.load 参数为库文件的绝对路径,可以是任意路径。
例如你可以这样载入一个windows平台下JNI库文件:
System.load(“C://Documents and Settings//TestJNI.dll”);。 - System.loadLibrary 参数为库文件名,不包含库文件的扩展名。
例如你可以这样载入一个windows平台下JNI库文件
System. loadLibrary (“TestJNI”);
Biginteger
在Java中,由CPU原生提供的整型最大范围是64位long
型整数。使用long
型整数可以直接通过CPU指令进行计算,速度非常快。
如果我们使用的整数范围超过了long
型怎么办?这个时候,就只能用软件来模拟一个大整数。java.math.BigInteger
就是用来表示任意大小的整数。BigInteger
内部用一个int[]
数组来模拟一个非常大的整数
此处仅作简单介绍,详细了解参考如下的教程网站Bitinteger