1. 本文讲述WIN32下的calling convention,也就是__cdecl, __stdcall, __fastcall这些编译器指示代码。本文所有的内容都是针对WIN32的,准确点说,是针对windows的C/C++编译器的。UNIX /Linux下基于GNU编译器的,就没有这个东西(可能是就一种calling convention吧),不过GNU编译器也有一个有趣的编译指令-__attribute__,有兴趣的可以参考Linux版中的“Using GNU C __attribute__”一文。

2. 所谓calling convention(呼叫约定)其实是我们代码中对编译器行为的一些指令。比如函数的参数如何进栈,函数执行完成,参数何时出栈等。所以,如果我们在代 码中不清楚这些东西而导致我们在定义函数和调用函数的时候使用的呼叫约定不匹配,将导致程序出错!下面是详细的解释。

3. 首先我们来看__cdecl,这是C函数的传统呼叫约定。他的逻辑是这样的:

/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"

可以看到,__cdecl下,首先将参数进栈,然后call这个function,完成 后将参数全部pop出堆栈。__cdecl是微软编译器的默认呼叫约定。当然,我们可以在我们代码的project中指定默认的call convention,也就是说,如果一个函数申明的时候,没有指定call convention,就是用project中设置的这个。

4. 但是我们在windows编程的过程中,经常会碰到另外一个呼叫约定-__stdcall,比如我们在申明WinMain函数的时候,前面加上的 WINAPI,其实就是__stdcall,这种呼叫方式我们称为PASCAL呼叫约定。和__cdecl不同的是,__stdcall呼叫约定不需要函 数手动将参数出栈,他通过callee统一负责把参数弹出堆栈:

/* example of __stdcall */
push arg1
push arg2
push arg3
call function
// no stack cleanup - callee does this

OK,看到这里应该已经明白了,如果我们在写代码的时候,不注意呼叫约定,在定义函数和调用函数的时候使用呼叫约定不匹配的话,会导致堆栈遭破坏,从而整个程序执行出错!!

5. 还有一个呼叫约定常用的叫__fastcall,这里我们并不过多涉及。这个呼叫约定使用CPU register来存放参数从而达到高效的目的,但事实上,实际效果并没有宣称的那么高性能。本文我们关注的是__cdecl, __stdcall。

6. OK,从上面的内容,我们可以发现这样一些结论:

(1) 使用__cdecl,我们的二进制代码如果用汇编来看的话,会发现每个函数调用的后面,都会跟着一两句“参数出栈”的代码,这会导致二进制代码的文件大小增加,相比与使用__stdcall--一般要在程序很大的时候才能明显看出。

(2) 对于一些可变参数的函数,比如printf,这种函数的参数个数不确定,所以,对于这种函数,我们就不能使用__stdcall,很明显,因为 __stdcall使用callee来做“参数出栈”的动作,对于这样的函数,callee无法确定要将多少个参数出栈,所以我们只能使用__cdecl

(3) 其实我们关注的不是__cdecl, __stdcall本身,我们关注的是在我们的代码中,我们要确保函数的呼叫约定是一致的(match的),否则就会导致堆栈被破坏,从而程序出现严重错误。

7. Linker symbol name decorations. 从上面我们可以看到,如果我们使用呼叫约定不匹配的话,会带来非常严重的后果。为了解决这样的问题,微软做了一些工作(应该说是微软的编译器)。这就是 symbol name decoration. 针对不同的呼叫约定,微软编译器给函数定义不同的名字(decoration),从而避免呼叫约定不匹配的问题。这也是为什么我们的代码经过编译后,函数 的名字发生了变化的原因:

(1) 对于__cdecl(编译器开关是cl /Gd),微软编译器在编译的时候,将这种呼叫约定的函数名改成 _<function name>

(2) 对于__stdcall(编译器编译选项是cl /Gz), 微软编译器在编译的时候,将这种呼叫约定的函数名改成 _<function name>@<所有参数的字节数>

(3) 对于__fastcall(编译器编译选项是cl /Gr), 微软编译器在编译的时候,将这种呼叫约定的函数名改成 @<function name>@<所有参数的字节数>

形象点,看下面的图片:

附件1

8. 通过上面的做法,我们看到,不同的呼叫约定,编译器为我们生成的函数名字不一样,这样在一定程度上避免了呼叫约定的错用。为什么能避免呼叫约定的误用呢?我们可以来考虑这样的一个例子:

我 们的代码需要使用到一个第三方的函数库。这个函数库提供了.h文件和.lib文件,但是在.h文件中,函数前没有指定call convention,也就是说,这个函数的calling convention依赖于我们这个Project中指定的calling convention(在Visual Studio的Project Properties对话框中,在C/C++选项下的calling convention一项中可以指定),假设是__cdecl吧。OK,这样,我们的代码编译完成后,对这个第三方的库的函数的调用就是__cdecl方 式,但是很可能,这个第三方的库在编译的时候,函数用的是__stdcall的呼叫约定,此时程序就有可能出错。但是我们说道了,微软的编译器有 symbol name decoration,这样一来,在我们的代码中,实际要调用的这个第三方的函数是 "_<function name>",但是在lib中,这个函数的名字是 "_<function name>@<parameter byte count>",这是不匹配的,所以在程序Link的时候,就会出错,这样就避免了呼叫约定不匹配的问题!

9. 调用DLL中的函数。DLL有点特殊,他没有.h文件,也没有lib文件,我们用LoadLibrary这个API来载入dll中的函数。此时我们不知道 dll中这些函数的call convention,在这个时候,请参考DLL的文档,确保使用的call convention是正确的!!

10. Good Practise. 我们在写代码的时候,这里给出一些有关call convention的good practise。

(1) Rule 1: Library headers should explicitly name a calling convention everywhere — Do not rely on the default. 所有的函数都手动指定call convention,不要依赖于呼叫约定的默认值。这是最好的做法了。这里注意,对于一个函数,我们可以在函数申明的时候定义该函数的呼叫约定,也可以 在函数的实现部分,定义call convention,那么,哪个为准呢?--定义在函数申明处的call convention为准。比如:

    /* somefile.c */

extern void __stdcall foo1(void);
    ..
    void foo1(void)      // OK - __stdcall taken from the declaration just seen
    {
       ...
    }

extern void __stdcall foo2(void);
    ...
    void __cdecl foo2(void) // ERROR - clashes with __stdcall above
    {
       ...
    }

extern void foo3(void);   // presume __cdecl
    ...
    void __stdcall foo3(void) // ERROR - clashes with presumed __cdecl
    {
       ...
    }

(2) Rule 2: Use the C preprocessor and a portability-related header file to make this work seamlesly on non-MSVC platforms. 在非微软的OS上,比如UNIX,Linux,是没有__stdcall, __cdecl这些东西的,那么如何保证我们的代码的可移植性呢?--使用PreProcessor,我们可以定义一个portable.h文件:

#ifndef _WIN32
# define __cdecl /* nothing */
# define __stdcall /* nothing */
# define __fastcall /* nothing */
#endif /* _WIN32 */

11. 总结一下。Calling convention其实不是一个非常复杂的东西,但是误用他会让我们的程序堆栈损坏,程序出现严重错误。同时,程序员如果不清楚calling convention,在编译和调试程序的时候会抓狂的。--为什么Linker就是找不到一个函数呢?

我们在书写代码的时候,尽量做到 每个函数都带有明确的Calling convention定义。特别是如果写的是一个给别人用的Libary,那么开放给使用者的函数最好必须明确定义calling convention,那么不开放给外部的函数,自己用的函数自己清楚即可。

在调用第三方的函数的时候,一定要搞清楚,我们调用的函数的calling convention!不要误用。

函数的呼叫约定以定义在函数申明时候的为准。如果一个函数没有申明,直接就是实现代码,那么以实现代码中定义的呼叫约定为准。没有定义任何呼叫约定,以编译的时候给出的/G(x)这个编译选项为准,如果这个编译选项也没有,那么呼叫约定默认取成__cdecl。

转载于:https://www.cnblogs.com/super119/archive/2011/04/10/2011304.html

Using WIN32 calling convention 阅读笔记相关推荐

  1. 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型

    1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...

  2. 【Flink】Flink 源码阅读笔记(15)- Flink SQL 整体执行框架

    1.概述 转载:Flink 源码阅读笔记(15)- Flink SQL 整体执行框架 在数据处理领域,无论是实时数据处理还是离线数据处理,使用 SQL 简化开发将会是未来的整体发展趋势.尽管 SQL ...

  3. go源码阅读笔记(unsafe)

    go源码阅读笔记(unsafe) unsafe 包主要是可以使得用户绕过go的类型规范检查,能够对指针以及其指向的区域进行读写操作. package mathimport "unsafe&q ...

  4. trainer setup_Detectron2源码阅读笔记-(一)Configamp;Trainer

    一.代码结构概览 1.核心部分 configs:储存各种网络的yaml配置文件 datasets:存放数据集的地方 detectron2:运行代码的核心组件 tools:提供了运行代码的入口以及一切可 ...

  5. VoxelNet阅读笔记

    作者:Tom Hardy Date:2020-02-11 来源:VoxelNet阅读笔记

  6. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  7. 源码阅读笔记 BiLSTM+CRF做NER任务 流程图

    源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...

  8. Mina源码阅读笔记(一)-整体解读

    2019独角兽企业重金招聘Python工程师标准>>> 今天的这一节,将从整体上对mina的源代码进行把握,网上已经有好多关于mina源码的阅读笔记,但好多都是列举了一下每个接口或者 ...

  9. “CoreCLR is now Open Source”阅读笔记

    英文原文:CoreCLR is now Open Source 阅读笔记如下: CoreCLR是.NET Core的执行引擎,功能包括GC(Garbage Collection), JIT(将CIL代 ...

  10. QCon 2015 阅读笔记 - 团队建设

    QCon 2015阅读笔记 QCon 2015 阅读笔记 - 移动开发最佳实践 QCon 2015 阅读笔记 - 团队建设 中西对话:团队管理的五项理论和实战 - 谢欣.董飞(今日头条,LinkedI ...

最新文章

  1. [转]几种最短路径算法的比较
  2. oracle cbo 查询展开,Oracle CBO几种基本的查询转换详解
  3. 最新以及历史各版本 .NET Framework 的下载
  4. 谷歌这波操作,预警了什么信号??
  5. 2021年兰州师大附中高考成绩查询,西北师范大学附属中学2021年排名
  6. JavaScript高级程序设计学习(四)之引用类型(续)
  7. 3d在调试区输出坐标_CSS3如何实现一个 3D 效果的魔方
  8. Linux中用户与组群管理
  9. 鼠标和按键在android 上的识别和区别
  10. FFmpeg进行屏幕录像和录音
  11. C++--第22课 - 类模板 - 下
  12. 使用KELI调试单片机代码时,一定要用Open Project的方式打开。
  13. Android实现两台手机屏幕共享和远程控制
  14. 智能数据中心和智慧园区:华为的单点突破与全局效应
  15. 自动化测试框架RobotFrameWork教程03 RF基础关键字
  16. 如何给生成的exe加图标
  17. Java基础项目实战--大学生求职招聘信息网站系统
  18. android 获取栈顶activity,android获取当前栈顶的activity
  19. 查询选修了95002选修的全部课程的学生学号。
  20. office提示为什么要冒险的解决办法

热门文章

  1. Linux系统是否被植入木马的排查流程梳理
  2. git学习中遇到的疑难杂症
  3. MySQL操作(备份很重要)
  4. 快速部署简单私有云CloudStack(下)
  5. 支持“***Context”上下文的模型已在数据库创建后发生更改
  6. Python-函数递归-二分法
  7. WordPress多语言插件
  8. Cesium应用篇:3控件(1)Clock
  9. TortoiseGit disconnected: no supported authentication methods available(server sent:publickey)
  10. 布局优化之ViewStub、Include、merge使用分析