_cdecl 与 _stdcal 区别 以及 extern “c‘ 作用

2023-11-12 01:36

本文主要是介绍_cdecl 与 _stdcal 区别 以及 extern “c‘ 作用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 目的
    • _stdcal
    • _cdecl
    • 结果
    • dll extern "c"
    • 补充

目的

1) 了解windows 下 _cdecl 与 _stdcal区别, 方便更好运用修饰函数进行函数调用时,有几种调用方法,分为C式,Pascal式。总的区别来说。

  • _cdecl(c/c++缺省的,支持变参函数,但其他非c/c++语言不支持)
  • _stdcall(依词义,标准调用,不支持变参)

2) dll extern "c‘ 作用

_stdcal

WINAPI (实际上就是PASCAL,CALLBACK,_stdcall)

  void   WINAPI   Input(   int   &m,int   &n);  

**特点:**在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数所占字节数(10进制),如:void Input(int &m,int &n),被修饰成:_Input@8
对于大多数api函数以及窗口消息处理函数皆用 CALLBACK ,所以调用前,主调函数会先压栈,然后api函数自己恢复堆栈。

_cdecl

在C和C++中C式调用是缺省的,除非特殊声明。

  void   Input(   int   &m,int   &n);  

特点: 在C或C++语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复堆栈,实参的压栈顺序是从右到左,最后由主调函数进行堆栈恢复。由于主调用函数管理堆栈,所以可以实现变参函数。另外,命名修饰方法是在函数前加一个下划线(_).

证明实际上引用传递的是变量的地址(类似指针)

结果

__stdcall 与 _cdecl

The __stdcall calling convention is used to call Win32 API functions.The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype. The __stdcall modifier is Microsoft-specific.

__stdcall 调用约定用于调用 Win32 API 函数。被调用者清理堆栈,因此编译器将可变参数函数设为 __cdecl。使用此调用约定的函数需要函数原型。 __stdcall 修饰符是 Microsoft 特定的。

_cdecl 与 _stdcall 详解

__stdcall

dll extern “c”

动态链接库的使用有两种方式,一种是显式调用。一种是隐式调用。

  • 显式调用:使用LoadLibrary载入动态链接库、使用GetProcAddress获取某函数地址。
  • 隐式调用:可以使用#pragma comment(lib, “XX.lib”)的方式,也可以直接将XX.lib加入到工程中。

dll编写需要解决函数重命名——Name-Mangling。解决方式有两种,一种是直接在代码里解决采用extent”c”、_declspec(dllexport)、#pragma comment(linker, “/export:[Exports Name]=[Mangling Name]”),另一种是采用def文件。

因为C和C++的重命名规则是不一样的。这种重命名称为“Name-Mangling”(名字修饰或名字改编、标识符重命名)。

据说,C++标准并没有规定Name-Mangling的方案,所以不同编译器使用的是不同的,例如:Borland C++跟Mircrosoft C++就不同,而且可能不同版本的编译器他们的Name-Mangling规则也是不同的。这样的话,不同编译器编译出来的目标文件.obj 是不通用的,因为同一个函数,使用不同的Name-Mangling在obj文件中就会有不同的名字。如果DLL里的函数重命名规则跟DLL的使用者采用的重命名规则不一致,那就会找不到这个函数。

C标准规定了C语言Name-Mangling的规范(林锐的书有这样说过)。这样就使得,任何一个支持C语言的编译器,它编译出来的obj文件可以共享,链接成可执行文件。这是一种标准,如果DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误。

影响符号名的除了C++和C的区别、编译器的区别之外,还要考虑调用约定导致的Name Mangling。如extern “c” __stdcall的调用方式就会在原来函数名上加上写表示参数的符号,而extern “c” __cdecl则不会附加额外的符号。

dll中的函数在被调用时是以函数名或函数编号的方式被索引的。这就意味着采用某编译器的C++的Name-Mangling方式产生的dll文件可能不通用。因为它们的函数名重命名方式不同。为了使得dll可以通用些,很多时候都要使用C的Name-Mangling方式,即是对每一个导出函数声明为extern “C”,而且采用_stdcall调用约定,接着还需要对导出函数进行重命名,以便导出不加修饰的函数名。

注意到extern “C”的作用是为了解决函数符号名的问题,这对于动态链接库的制造者和动态链接库的使用者都需要遵守的规则。

动态链接库的显式装入就是通过GetProcAddress函数,依据动态链接库句柄和函数名,获取函数地址。因为GetProcAddress仅是操作系统相关,可能会操作各种各样的编译器产生的dll,它的参数里的函数名是原原本本的函数名,没有任何修饰,所以一般情况下需要确保dll’里的函数名是原始的函数名。分两步:一,如果导出函数使用了extern”C” _cdecl,那么就不需要再重命名了,这个时候dll里的名字就是原始名字;如果使用了extern”C” _stdcall,这时候dll中的函数名被修饰了,就需要重命名。二、重命名的方式有两种,要么使用*.def文件,在文件外修正,要么使用#pragma,在代码里给函数别名。

  • _declspec(dllexport)和_declspec(dllimport)的作用

    _declspec还有另外的用途,这里只讨论跟dll相关的使用。正如括号里的关键字一样,导出和导入。_declspec(dllexport)用在dll上,用于说明这是导出的函数。而_declspec(dllimport)用在调用dll的程序中,用于说明这是从dll中导入的函数。

     因为dll中必须说明函数要用于导出,所以_declspec(dllexport)很有必要。但是可以换一种方式,可以使用def文件来说明哪些函数用于导出,同时def文件里边还有函数的编号。
    

而使用_declspec(dllimport)却不是必须的,但是建议这么做。因为如果不用_declspec(dllimport)来说明该函数是从dll导入的,那么编译器就不知道这个函数到底在哪里,生成的exe里会有一个call XX的指令,这个XX是一个常数地址,XX地址处是一个jmp dword ptr[XXXX]的指令,跳转到该函数的函数体处,显然这样就无缘无故多了一次中间的跳转。如果使用了_declspec(dllimport)来说明,那么就直接产生call dword ptr[XXX],这样就不会有多余的跳转了

  • __stdcall影响

这是一种函数的调用方式。默认情况下VC使用的是__cdecl的函数调用方式,如果产生的dll只会给C/C++程序使用,那么就没必要定义为__stdcall调用方式,如果要给Win32汇编使用(或者其他的__stdcall调用方式的程序),那么就可以使用__stdcall。这个可能不是很重要,因为可以自己在调用函数的时候设置函数调用的规则。像VC就可以设置函数的调用方式,所以可以方便的使用win32汇编产生的dll。不过__stdcall这调用约定会Name-Mangling,所以我觉得用VC默认的调用约定简便些。但是,如果既要__stdcall调用约定,又要函数名不给修饰,那可以使用*.def文件,或者在代码里#pragma的方式给函数提供别名(这种方式需要知道修饰后的函数名是什么)。

·extern “C” __declspec(dllexport) bool  __stdcall cswuyg();
·extern “C”__declspec(dllimport) bool __stdcall cswuyg();
·#pragma comment(linker, "/export:cswuyg=_cswuyg@0")

结论:
总的来说,在编写DLL的时候,写个头文件,头文件里声明函数的NameMingling方式、调用约定(主要是为了隐式调用)。再写个
.def文件把函数重命名了(主要是为了显式调用)。提供
.DLL*.lib*.h给dll的使用者,这样无论是隐式的调用,还是显式的调用,都可以方便的进行。**

很感谢这位博客主写的DLL编写中extern “C”和__stdcall的作用 他写的详细和很清楚了。

补充

1)调用协议常用场合

  • __stdcall:Windows API默认的函数调用协议。
  • __cdecl:C/C++默认的函数调用协议。
  • __fastcall:适用于对性能要求较高的场合。

2)函数参数入栈方式

  • __stdcall:函数参数由右向左入栈。
  • __cdecl:函数参数由右向左入栈。
  • __fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。
    问题一:__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。

3)栈内数据清除方式

  • __stdcall:函数调用结束后由被调用函数清除栈内数据。
  • __cdecl:函数调用结束后由函数调用者清除栈内数据。
  • __fastcall:函数调用结束后由被调用函数清除栈内数据。
    问题一:不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
    问题二:某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
    问题三:由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。

4)C语言编译器函数名称修饰规则

  • __stdcall:编译后,函数名被修饰为“_functionname@number”。
  • __cdecl:编译后,函数名被修饰为“_functionname”。
  • __fastcall:编译后,函数名给修饰为“@functionname@nmuber”。
    注:“functionname”为函数名,“number”为参数字节数。
    注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

5)C++语言编译器函数名称修饰规则

  • __stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。
  • __cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。
  • __fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。
    注:“******”为函数返回值类型和参数类型表。
    注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

6)C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。

这篇关于_cdecl 与 _stdcal 区别 以及 extern “c‘ 作用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/394198

相关文章

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

JAVA覆盖和重写的区别及说明

《JAVA覆盖和重写的区别及说明》非静态方法的覆盖即重写,具有多态性;静态方法无法被覆盖,但可被重写(仅通过类名调用),二者区别在于绑定时机与引用类型关联性... 目录Java覆盖和重写的区别经常听到两种话认真读完上面两份代码JAVA覆盖和重写的区别经常听到两种话1.覆盖=重写。2.静态方法可andro

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化