C/C++函数的调用约定的使用

目录
  • 1、概述
  • 2、常见的调用约定说明
    • 2.1、__cdecl C调用
    • 2.2、__stdcall标准调用
    • 2.3、__fastcall快速调用
    • 2.4、__thiscall调用
  • 3、调用约定不一致导致的软件异常问题
  • 4、与调用约定相关的工程配置选项及/RTC编译选项

函数的调用约定其实比较简单,并不复杂,但很多人对这一块内容不太了解,甚至连工作几年的朋友也不太清楚。最近有朋友想了解这一块的内容,所以今天我们就来讲一下C/C++函数调用约定相关的内容。

1、概述

常见的函数调用约定有__cdecl C调用、__stdcall标准调用、__fastcall快速调用以及__pascal调用:

这些调用是开发语言中的关键字,放置在函数前,用来指定函数的调用约定,比如:

BOOL __stdcall InitSDK();

如上所示,调用约定关键字一般放于返回值类型与函数之间。而函数返回值类型前面一般放置函数的导入导出声明:(dll库的函数接口有导入导出之分)

// 定义导出导入SDK_DLL_API宏
#ifdef DLL_EXPORTS
#define SDK_DLL_API _declspec(dllexport)
#else
#define SDK_DLL_API _declspec(dllimport)
#endif

// 将导出导入SDK_DLL_API宏放到返回值类型之前
SDK_DLL_API BOOL __stdcall InitSDK();

函数的调用约定主要决定三方面的内容:

1)函数参数的入栈顺序

函数调用时主调函数的参数是通过栈传递给被调用函数的。从汇编上看的比较清晰,在call函数之前,会将参数的值压到栈上,比如:

如果函数有多个参数,则会有两种入栈方式,一种是从右到左依次入栈,一种是从左到右依次入栈,这是函数调用约定决定的。

2)参数栈空间由谁来释放

函数调用完成后传递给被调用函数的参数的占用的栈空间是需要释放掉的,专业术语叫“平栈”,清理掉参数的栈空间才能做到栈平衡。参数占用的栈空间到底是谁来清理,也是函数调用约定决定的。编译器在编译链接生成汇编代码时,就生成好了清理参数栈空间的汇编代码。

3)编译时的函数名称改编

不同的调用约定下编译生成的函数名称格式可能是不同的。C++之所以支持函数重载(源代码中,函数名称相同,函数参数不同),就是因为C++编译器会对函数名称进行改编,改编后的名称中包含参数类型进而能区分出重载的函数。

2、常见的调用约定说明

常见的函数调用约定有__cdecl C调用、__stdcall标准调用、__fastcall快速调用以及__pascal调用。C/C++ 中主要使用__cdecl C调用、__stdcall标准调用、__fastcall快速调用三种。__pascal 是用于 Pascal / Delphi 编程语言的调用规则,C/C++ 中也可以使用这种调用规则,但该调用约定已经被C++废弃,不提倡使用了。

下面我们来看看这几种调用约定的异同点,见下面的表格:

2.1、__cdecl C调用

它是C/C++函数默认的调用规范,C/C++运行时库中的函数基本都是__cdecl调用。在该调用约定下,参数从右向左依次压入栈中,由主调函数负责清理参数的栈空间。该调用约定适用于支持可变参数的函数,因为只有主调函数才知道给该种函数传递了多少个参数,才知道应该清理多少栈空间。比如支持可变参数的C函数printf:

int __cdecl printf ( const char *format, ... )
{
    va_list arglist;
    int buffing;
    int retval;

    _VALIDATE_RETURN( (format != NULL), EINVAL, -1);

    va_start(arglist, format);

    _lock_str2(1, stdout);

    __try {
        buffing = _stbuf(stdout);

        retval = _output_l(stdout,format,NULL,arglist);

        _ftbuf(buffing, stdout);

    }
    __finally {
        _unlock_str2(1, stdout);
    }

    return(retval);
}

2.2、__stdcall标准调用

它是Windows系统提供的系统API函数的调用约定,比如API函数GetWindowText的声明如下:

WINUSERAPI
int
WINAPI
GetWindowTextW(
    _In_ HWND hWnd,
    _Out_writes_(nMaxCount) LPWSTR lpString,
    _In_ int nMaxCount);

其中,WINAPI宏就是__stdcall标准调用,即:

#define WINAPI __stdcall

同时__stdcall也是很多提供给第三方使用的SDK库的API接口的调用约定。在该调用约定下,参数从右向左依次压入栈中,由被调用函数负责清理栈空间。如果函数是可变参的,函数的调用约定会自动转化为__cdecl调用。

2.3、__fastcall快速调用

该调用约定之所以被称作为快速调用,因为有部分参数可以通过寄存器直接传递,效率比较高。对于内存大小小于等于4字节的参数,直接使用ECX和EDX寄存器传递,剩余的参数则依次从右到左压入栈中通过栈传递,参数传递占用的栈空间由被调用函数清理。

2.4、__thiscall调用

__thiscall是C++中的非静态类成员函数的默认调用约定。该调用约定也用到了寄存器传参,在调用C++类的非静态成员函数时会传入当前类对象的地址,该地址通过ECX寄存器来传递的。在该调用约定下,函数的参数按照从右到左的顺序入栈,被调用的函数在返回前清理参数的栈空间。

3、调用约定不一致导致的软件异常问题

以前我们将C++开发的SDK库提供给第三方厂商做二次开发,第三方客户使用的是C#语言,即C#开发的程序去调用C++开发的SDK库,当时因为SDK头文件中声明的回调函数没有指定调用约定,导致程序出现异常崩溃的问题。

我们C++开发的SDK提供了设置消息回调的API接口,并给出了回调函数的声明,如下:

/* 函数功能:用于消息回发的回调函数指针(服务器主动推送的消息通过该回调函数推给上层)
   参数:DWORD dwMsgId:消息id
         const unsigned char* pMsgBuf:消息中携带的数据buffer,buffer中的具体内容取决于消息id,参看消息id的头文件
                 DWORD dwMsgBufLen:消息中携带的数据buffer长度
   返回值:void
*/
typedef void (*PMsgCallBackFunc)( DWORD dwMsgId, const unsigned char* pMsgBuf, DWORD dwMsgBufLen );

设置回调函数的接口如下:

// 设置业务消息回调接口
SDK_DLL_API void __stdcall SetMsgCallBack( IN PMsgCallBackFunc pMsgCallBackFunc );

回调函数的实现在上层的C#程序中,回调函数的调用在C++实现的SDK中,因为回调函数PMsgCallBackFunc在声明时没有指定函数调用约定,在C#程序中默认是__stdcall标准约定,所以在C#中编译时回调函数内部会清理栈空间。而回调函数是在C++ SDK中调用的,在SDK编译时默认是__cdecl调用,会在调用回调函数处的主调函数中释放栈空间,这样导致回调函数调用后,主调函数会释放一次栈空间,回调函数内部会释放一次栈空间,所以多释放了一次参数栈空间,导致了栈不平衡,导致程序运行出异常。

考虑跨语言调用的场景,SDK要提供标准的C接口。在SDK的头文件中,SDK导出接口要指定调用约定,回调函数的声明也要指定调用约定。

4、与调用约定相关的工程配置选项及/RTC编译选项

在Visual Studio创建的C++工程中,在没明确指定函数调用约定时,默认使用的都是__cdecl调用,我们可以在工程属性配置中看到:

对于C++工程,我们一般不需要修改默认的调用约定。如果要指定dll库导出接口的调用约定,我们也不需要修改工程配置,只需要在导出接口的头文件的函数声明处指定调用约定就可以了。

有人可能会说,工程属性配置中使用了默认的__cdecl调用,我们又在头文件中将接口指定为__stdcall标准调用,会不会有冲突?到底以哪个为准呢?没有冲突的,编译时是优先以接口声明处指定的调用约定为准的。

在Debug下/RTC运行时检测编译选项是默认开启的,/RTC运行时检测在函数调用完成后会去检测栈是否平衡,关于这一点的说明如下:(MSDN上对/RTC编译选项的说明)

如果没有释放参数的栈空间或者参数栈空间多释放了一次,都能检测出来。如果检测到,会弹出如下的提示:

到此这篇关于C/C++函数的调用约定的使用的文章就介绍到这了,更多相关C/C++函数调用约定内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C和C++的函数调用约定你知道多少

    目录 调用方式 1.__cdecl 2.__stdcall 3.__fastcall 4.naked 5.__pascal 6.__thiscall 名字修饰约定 1.C编译时函数名修饰约定规则 2.C++编译时函数名修饰约定规则 总结 调用方式 C/C++函数有多种调用约定. C语言: __cdecl __stdcall __fastcall naked __pascal C++比C语言多了一种: __thiscall 1. __cdecl __cdecl调用约定又称为C调用约定,时C/C++

  • C/C++函数的调用约定的使用

    目录 1.概述 2.常见的调用约定说明 2.1.__cdecl C调用 2.2.__stdcall标准调用 2.3.__fastcall快速调用 2.4.__thiscall调用 3.调用约定不一致导致的软件异常问题 4.与调用约定相关的工程配置选项及/RTC编译选项 函数的调用约定其实比较简单,并不复杂,但很多人对这一块内容不太了解,甚至连工作几年的朋友也不太清楚.最近有朋友想了解这一块的内容,所以今天我们就来讲一下C/C++函数调用约定相关的内容. 1.概述 常见的函数调用约定有__cdec

  • 解析go语言调用约定多返回值实现原理

    目录 go简单代码反汇编 go语言调用约定分析 1.C/C++调用约定类别 2.go语言调用约定 go语言如何实现多返回值的 总结 go简单代码反汇编 用简单的代码用以分析go的调用约定及多返回值的返回方式. package main func vals(c, d int) (a int, b int) { e := 1 f := 2 a = c + d + e + f b = d * 2 return } func testMutil() { i, j := vals(1, 2) i = i

  • setinterval()与clearInterval()JS函数的调用方法

    本文实例讲述了setinterval()与clearInterval()JS函数的调用方法.分享给大家供大家参考.具体如下: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.

  • 浅析JavaScript函数的调用模式

    我们说一个函数的调用模式是作为一个函数来调用,是要与其它三种调用模式做区分 函数其他的三种调用: 方法调用模式,构造器调用模式,apply/call调用模式. 方法的调用模式:  var obj={ fun1: function(){ //方法内容 this; //指的是window } } obj.fun1() //方法的调用 构造器的调用: function Person(name, age, job){ this.name = name; this.age = age; this.job

  • PowerShell中简单的自定义函数和调用函数例子

    在PowerShell中是否有函数?PowerShell是否可以自定义函数?PowerShell中如何自定义函数? 在PowerShell中,我们可以使用函数(function)来简化编程开发.在PowerShell中使用function这个关键词来标识一个函数.一个自定义的函数,由function关键词开始,然后用一对大括号来包括起整个函数体的内容. 我们先来看一下简单的PowerShell函数: 复制代码 代码如下: function Test-Function {     Write-Ho

  • getJSON调用后台json数据时函数被调用两次的原因猜想

    近期在做前端开发时候使用到getJSON调用后台去json数据,发现后台的函数被调用两次,函数名称为getMessages, 多方调查结合网上兄弟经验发现,只要函数名不以get开头就没这个问题了, 本人大胆猜测,应该是请求返回的时候构造json数据时,调用所有get开头的函数,然后取得返回值然后构造响应. 所以,以get开头的函数做action的函数时,首先响应请求调用了一次,然后构造响应又调用了一次.

  • 一个Js文件函数中调用另一个Js文件函数的方法演示

    我们知道,在html中,利用<script language="javascript" type="text/javascript" src="./script.js"></script>引入的两个js是不可以相互调用的.那么该如何解决呢?当然,你可以将代码通通copy过来,也许你并不喜欢这样. 例如有这样一个html,里面有一个按钮,当按下时调用b.js文件中的方法b().而b()中又要调用a.js文件中的方法a().若

  • javascript嵌套函数和在函数内调用外部函数的区别分析

    我们都知道在函数中定义的局部变量在声明他的函数体以及其嵌套的函数内始终是有定义的,并且在函数的作用域链上始终会有个对象指向全局对象,使函数能够访问到全局变量. var ga = 'global'; var func = function() { var la = 'local'; return function() { return function() { return function() { alert(la);alert(ga); } } } } a = func(); a()()();

  • JavaScript函数的调用以及参数传递

    JavaScript 函数调用 JavaScript 函数有 4 种调用方式. 每种方式的不同方式在于 this 的初始化. this 关键字 一般而言,在Javascript中,this指向函数执行时的当前对象. Note 注意 this 是保留关键字,你不能修改 this 的值. 调用 JavaScript 函数 函数中的代码在函数被调用后执行. 作为一个函数调用 实例 function myFunction(a, b) { return a * b; } myFunction(10, 2)

  • js匿名函数的调用示例(形式多种多样)

    匿名函数就是没有实际名字的函数. javaScript的匿名函数形式多样,而且不搞清楚,容易看晕代码. 以下是成功调用的匿名函数: 复制代码 代码如下: (function () { alert(3); }) (); (function f1() { alert(4); })(); //不是匿名函数也能这样调用!! void function(){ alert('void water'); }();//据说效率最高,Javascript中void是一个操作符,该操作符指定要计算一个表达式但是不返

随机推荐