网站建设的平台,做微课的网站,网站建设建设多少钱,淘宝佣金推广网站建设div classmarkdown_viewsp有一定C开发经验的人一定对”__cdecl、__stdcall、__fastcall”肯定不陌生吧#xff01;但你真正理解了吗#xff1f;是的#xff0c;我曾在这采了无数个坑#xff0c;栽了无数个跟头#xff0c;终于忍无可忍要把它总… div classmarkdown_viewsp有一定C开发经验的人一定对”__cdecl、__stdcall、__fastcall”肯定不陌生吧但你真正理解了吗是的我曾在这采了无数个坑栽了无数个跟头终于忍无可忍要把它总结一下(虽然我已经有能力解决大部分这种问题了)!/p什么是调用约定 函数的调用约定顾名思义就是对函数调用的一个约束和规定(规范)描述了函数参数是怎么传递和由谁清除堆栈的。它决定以下内容(1)函数参数的压栈顺序(2)由调用者还是被调用者把参数弹出栈(3)以及产生函数修饰名的方法。 我们知道函数由以下几部分构成:返回值类型 函数名(参数列表)如: 【code1】
void function();
int add(int a, int b);以上是大家所熟知的构成部分其实函数的构成还有一部分那就是调用约定。如下 【code2】
void __cdecl function();
int __stdcall add(int a, int b);上面的__cdecl和__stdcall就是调用约定其中__cdecl是C和C默认的调用约定所以通常我们的代码都如 【code1】中那样定义编译器默认会为我们使用__cdecl调用约定。常见的调用约定有__cdecl、__stdcall、fastcall应用最广泛的是__cdecl和__stdcall,下面我们会详细进行讲述。。还有一些不常见的如 __pascal、__thiscall、__vectorcall。
声明和定义处调用约定必须要相同
在VC中调用约定是函数类型的一部分因此函数的声明和定义处调用约定要相同不能只在声明处有调用约定而定义处没有或与声明不同。如下 【code3】 错误的使用一
int __stdcall add(int a, int b);
int add(int a, int b)
{return a b;
}报错 error C2373: ‘add’: redefinition; different type modifiers error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’ 【code4】 错误的使用二
int add(int a, int b);
int __stdcall add(int a, int b)
{return a b;
}报错 error C2373: ‘add’: redefinition; different type modifiers error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’ 【code5】 错误的使用三
int __stdcall add(int a, int b);
int __cdecl add(int a, int b)
{return a b;
}报错 error C2373: ‘add’: redefinition; different type modifiers error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’ 【code6】 正确的用法
int __stdcall add(int a, int b);
int __stdcall add(int a, int b)
{return a b;
}函数的调用过程
要深入理解函数调用约定你须要了解函数的调用过程和调用细节。 假设函数A调用函数B我们称A函数为”调用者”,B函数为“被调用者”。如下面的代码ShowResult为调用者add为被调用者。
int add(int a, int b)
{return a b;
}void ShowResult()
{std::cout add(5, 10) std::endl;
}函数调用过程可以这么描述 1先将调用者A的堆栈的基址ebp入栈以保存之前任务的信息。 2然后将调用者A的栈顶指针esp的值赋给ebp作为新的基址即被调用者B的栈底。 3然后在这个基址被调用者B的栈底上开辟一般用sub指令相应的空间用作被调用者B的栈空间。 4函数B返回后从当前栈帧的ebp即恢复为调用者A的栈顶esp使栈顶恢复函数B被调用前的位置然后调用者A再从恢复后的栈顶可弹出之前的ebp值可以这么做是因为这个值在函数调用前一步被压入堆栈。这样ebp和esp就都恢复了调用函数B前的位置也就是栈恢复函数B调用前的状态。 这个过程在ATT汇编中通过两条指令完成即 leaveret这两条指令更直白点就相当于mov %ebp , %esppop %ebp此部分内容参考http://blog.csdn.net/zsy2020314/article/details/9429707
__cdecl的特点
__cdecl 是 C Declaration 的缩写表示 C 和 C 默认的函数调用约定。是C/C和MFCX的默认调用约定。
按从右至左的顺序压参数入栈、。由调用者把参数弹出栈。切记对于传送参数的内存栈是由调用者来维护的返回值在EAX中。因此对于像printf这样可变参数的函数必须用这种约定。编译器在编译的时候对这种调用规则的函数生成修饰名的时候在输出函数名前加上一个下划线前缀格式为_function。如函数int add(int a, int b)的修饰名是_add。
(1).为了验证参数是从右至左的顺序压栈的我们可以看下面这段代码Debug进行单步调试,可以看到我们的调用栈会先进入GetC()再进入GetB()最后进入GetA()。
(2).第二点“调用者把参数弹出栈”这是编译器的工作暂时没办法验证。要深入了解这部分需要学习汇编语言相关的知识。
(3).函数的修饰名这个可以通过对编译出的dll使用VS的”dumpbin /exports ProjectName.dll”命令进行查看(后面章节会进行详细介绍)或直接打开.obj文件查找对应的方法名(如搜索add)。
从代码和程序调试的层面考虑参数的压栈顺序和栈的清理我们都不用太观注因为这是编译器的决定的我们改变不了。但第三点却常常困扰我们因为如果不弄清楚这点在多个库之间(如dll、lib、exe)相互调用、依赖时常常出出现莫名其妙的错误。这个我在后面章节会进行详细介绍。
__stdcall的特点
__stdcall是Standard Call的缩写是C的标准调用方式,当然这是微软定义的标准__stdcall通常用于Win32 API中(可查看WINAPI的定义)。
按从右至左的顺序压参数入栈。由被调用者把参数弹出栈。切记函数自己在退出时清空堆栈返回值在EAX中。__stdcall调用约定在输出函数名前加上一个下划线前缀后面加上一个“”符号和其参数的字节数格式为_functionnumber。如函数int sub(int a, int b)的修饰名是_sub8。
__fastcall的特点
__fastcall调用的主要特点就是快因为它是通过寄存器来传送参数的。
实际上__fastcall用ECX和EDX传送前两个DWORD或更小的参数剩下的参数仍自右向左压栈传送被调用的函数在返回前清理传送参数的内存栈。__fastcall调用约定在输出函数名前加上一个“”符号后面也是一个“”符号和其参数的字节数格式为functionnumber,如double multi(double a, double b)的修饰名是multi16。__fastcall和__stdcall很象唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的即第1个参数进ECX第2个进EDX其他参数是从右向左的入栈返回仍然通过EAX。
以上内容参考http://www.3scard.com/index.php?mblogfviewid10
__thiscall
__thiscall是C类成员函数缺省的调用约定但它没有显示的声明形式。因为在C类中成员函数调用还有一个this指针参数因此必须特殊处理thiscall调用约定的特点
参数入栈参数从右向左入栈this指针入栈如果参数个数确定this指针通过ecx传递给被调用者如果参数个数不确定this指针在所有参数压栈后被压入栈。栈恢复对参数个数不定的调用者清理栈否则函数自己清理栈。
总结
这里主要总结一下_cdecl、_stdcall、__fastcall三者之间的区别
要点__cdecl__stdcall__fastcall参数传递方式右-左右-左左边开始的两个不大于4字节DWORD的参数分别放在ECX和EDX寄存器其余的参数自右向左压栈传送清理栈方调用者清理被调用函数清理被调用函数清理适用场合C/C、MFC的默认方式; 可变参数的时候使用;Win API要求速度快C编译修饰约定_functionname_functionnamenumberfunctionnamenumber
本文章转自luoweifu 带你玩转Visual Studio——绑定进程调试 * (function () {(function () { (pre.prettyprint code).each(function () { var lines (this).text().split(′\n′).length;var(this).text().split('\n').length; var numbering $(
).addClass(pre-numbering).hide(); (this).addClass(′has−numbering′).parent().append((this).addClass('has-numbering').parent().append(numbering); for (i 1; i lines; i) { numbering.append(numbering.append((
).text(i)); }; $numbering.fadeIn(1700); }); }); 欢迎使用Markdown编辑器写博客
本Markdown编辑器使用StackEdit修改而来用它写博客将会带来全新的体验哦
Markdown和扩展Markdown简洁的语法代码块高亮图片链接和图片上传LaTex数学公式UML序列图和流程图离线写博客导入导出Markdown文件丰富的快捷键 快捷键
加粗 Ctrl B 斜体 Ctrl I 引用 Ctrl Q插入链接 Ctrl L插入代码 Ctrl K插入图片 Ctrl G提升标题 Ctrl H有序列表 Ctrl O无序列表 Ctrl U横线 Ctrl R撤销 Ctrl Z重做 Ctrl Y
Markdown及扩展 Markdown 是一种轻量级标记语言它允许人们使用易读易写的纯文本格式编写文档然后转换成格式丰富的HTML页面。 —— [ 维基百科 ] 使用简单的符号标识不同的标题将某些文字标记为粗体或者斜体创建一个链接等详细语法参考帮助。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法
项目价格Computer$1600Phone$12Pipe$1
可以使用冒号来定义对齐方式
项目价格数量Computer1600 元5Phone12 元12Pipe1 元234
定义列表 Markdown Extra 定义列表语法项目项目定义 A 定义 B 项目定义 C 定义 D 定义D内容 代码块
代码块语法遵循标准markdown代码例如
requires_authorization
def somefunc(param1, param20):A docstringif param1 param2: # interestingprint Greaterreturn (param2 - param1 1) or None
class SomeClass:passmessage interpreter
... prompt
脚注
生成一个脚注1.
目录
用 [TOC]来生成目录 什么是调用约定声明和定义处调用约定必须要相同函数的调用过程 __cdecl的特点__stdcall的特点__fastcall的特点__thiscall总结快捷键Markdown及扩展表格定义列表代码块脚注目录数学公式UML 图 离线写博客浏览器兼容 数学公式
使用MathJax渲染LaTex 数学公式详见math.stackexchange.com.
行内公式数学公式为Γ(n)(n−1)!∀n∈N\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N。块级公式 x−b±b2−4ac−−−−−−−√2ax = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} 更多LaTex语法请参考 这儿.
UML 图:
可以渲染序列图
Created with Raphaël 2.1.0张三张三李四李四嘿小四儿, 写博客了没?李四愣了一下说忙得吐血哪有时间写。或者流程图
Created with Raphaël 2.1.0开始我的操作确认结束yesno关于 序列图 语法参考 这儿,关于 流程图 语法参考 这儿.
离线写博客
即使用户在没有网络的情况下也可以通过本编辑器离线写博客直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中内容实时保存在浏览器缓存中在用户关闭浏览器或者其它异常情况下内容不会丢失。用户再次打开浏览器时会显示上次用户正在编辑的没有发表的内容。
博客发表后本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱即使换浏览器或者清除缓存内容也不会丢失。 注意虽然浏览器存储大部分时候都比较可靠但为了您的数据安全在联网后请务必及时发表或者保存到服务器草稿箱。 浏览器兼容
目前本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。IE以下不支持IE存在以下问题 不支持离线功能IE9不支持文件导入导出IE10不支持拖拽文件导入 这里是 脚注 的 内容. ↩