【IT专家】为什么调用者必须在cdecl调用约定中清除堆栈?

合集下载

_stdcall介绍

_stdcall介绍

stdcall调用约定:stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。

在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。

stdcall调用约定声明的语法为(以前文的那个函数为例):int __stdcall function(int a,int b)stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。

以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:push 2 第二个参数入栈push 1 第一个参数入栈call function 调用参数,注意此时自动把cs:eip入栈而对于函数自身,则可以翻译为:push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebpret 8而在编译时,这个函数的名字被翻译成_function@8注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。

其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。

从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。

函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。

__stdcall用例 -回复

__stdcall用例 -回复

__stdcall用例-回复1. 什么是__stdcall?__stdcall是一种在应用程序开发中常见的函数调用约定,用于规定函数调用时参数的传递方式和返回值的返回方式。

它主要用于C和C++编程语言中,是一种Windows特定的函数调用约定。

2. __stdcall与其他函数调用约定有什么区别?__stdcall与其他函数调用约定,如__cdecl和__fastcall等,主要区别在于参数传递的方式和函数栈的清理方式上。

- __stdcall约定规定参数是从右向左依次压入堆栈,调用者负责在调用函数后清理堆栈。

- __cdecl约定规定参数是从右向左依次压入堆栈,但是堆栈的清理工作由被调用函数自己负责。

这使得__cdecl可以在不同编译器和库之间进行交互,但是会增加函数调用的开销。

- __fastcall约定将前两个整型参数存储在寄存器中,能够提高函数调用的性能。

但是这种约定只适用于少量的参数,过多的参数会导致其它参数被压入堆栈。

3. 为什么在Windows编程中常使用__stdcall?在Windows平台上,许多API函数使用了__stdcall约定,开发者需要遵守这个约定来正确调用这些函数。

这主要是为了确保编译器和函数库之间的兼容性,使得函数调用的参数传递和堆栈清理都能够正确执行。

此外,__stdcall约定还有以下优势:- 由于堆栈的清理工作由调用者来完成,可以确保在函数调用结束后堆栈的正确恢复,避免发生堆栈溢出等问题。

- 由于参数是从右向左依次压入堆栈,可以有效地避免参数溢出问题。

- 使用__stdcall约定的函数可以通过函数原型或函数指针的方式传递给其他函数,使得函数调用更加灵活和方便。

4. 如何定义一个使用__stdcall的函数?使用__stdcall约定定义一个函数,需要在函数声明的返回类型前加上__stdcall关键字。

例如:c__stdcall void MyFunction(int a, int b);在函数定义时也需要加上__stdcall关键字,例如:c__stdcall void MyFunction(int a, int b) {函数体}5. __stdcall约定可以有哪些限制?__stdcall约定在参数传递过程中有一些限制:- 参数必须是固定长度的类型,例如整型、指针等。

堆栈的工作原理

堆栈的工作原理

堆栈的工作原理
堆栈是一种数据结构,它遵循“先进后出”(LIFO)的原则。

它通常用于存储和管理函数调用、中断处理、内存分配等操作。

堆栈的工作原理如下:
1. 初始化堆栈:在使用堆栈之前,需要先分配一块固定大小的内存空间来存储堆栈中的元素。

这个空间可以是数组、链表或是其他数据结构。

2. 压栈(Push)操作:当有新的元素要加入堆栈时,它将被放置在堆栈的顶部。

这个过程被称为“压栈”,也就是将元素插入到堆栈的顶部。

3. 弹栈(Pop)操作:当需要访问堆栈中的元素时,可以从堆
栈的顶部开始弹出元素。

每次弹出的元素都是最新加入堆栈的那个元素,所以堆栈遵循了“先进后出”的原则。

4. 栈顶指针:堆栈通常使用一个指针来跟踪堆栈顶部的位置。

压栈操作会将栈顶指针向上移动,而弹栈操作会将栈顶指针向下移动。

5. 栈溢出:如果堆栈已满时还尝试进行压栈操作,就会发生栈溢出的错误。

栈溢出意味着堆栈已经超出了它的容量限制。

6. 栈空:如果堆栈中没有元素时,就称为栈空。

这时进行弹栈操作会导致错误,因为没有可弹出的元素。

堆栈的工作原理简单明了,它提供了一个高效的方式来存储和访问数据。

通过遵循“先进后出”的原则,堆栈可以灵活地支持各种场景下的数据管理需求。

堆栈及静态数据区详解

堆栈及静态数据区详解

堆、栈及静态数据区详解五大内存分区在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。

里面的变量通常是局部变量、函数参数等。

堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。

如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free 来结束自己的生命的。

全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)明确区分堆与栈在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。

首先,我们举一个例子:void f() { int* p=new int[5]; }这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。

在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:00401028 push 14h0040102A call operator new (00401060)0040102F add esp,400401032 mov dword ptr [ebp-8],eax00401035 mov eax,dword ptr [ebp-8]00401038 mov dword ptr [ebp-4],eax这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie 信息去进行释放内存的工作。

_stdcall与_cdel

_stdcall与_cdel

1._cdecl(1). 是C Declaration的缩写,表示C语言默认的函数调用方法,实际上也是C++的默认的函数调用方法。

(2). 所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。

具体所示:调用方的函数调用->被调用函数的执行->被调用函数的结果返回->调用方清除调整堆栈。

(3). 被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

总的来说函数的参数个数可变的(就像printf函数一样),因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。

(4). 因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。

2._stdcall(CALLBACK/WINAPI)(1). 是Standard Call的缩写,要想函数按照此调用方式必须在函数名加入_stdcall,通常_win32 api 应该是_stdcall调用规则。

通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为_stdcall 方式,WINAPI都采用这种方式。

(2). 所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。

具体所示:调用方的函数调用->被调用函数的执行-> 被调用方清除调整堆栈->被调用函数的结果返回。

(3). 这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。

称为自动清栈。

(4). 函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

总的来说,就是函数的参数个数不能是可变的。

是从_cdecl 修改而来, _stdcall 不支持可变参数,并且清栈由被调用者负责,其他的都一样(5). 因为只需在被调用函数的地方生成一段调整堆栈的代码,所以最后生成的文件较小。

【机试题】2014大疆嵌入式笔试题(附超详细解答,上篇)

【机试题】2014大疆嵌入式笔试题(附超详细解答,上篇)

【机试题】2014大疆嵌入式笔试题(附超详细解答,上篇)《2014大疆嵌入式笔试题》,这一份应该是全网搜得到的关于大疆嵌入式最完整的一份试题了。

只可惜,这一份试题,网上也只是有题目,却一直没有发现完整的答案什么的。

所以下面的解答主要都是博主结合网上的一些解答,总结出的见解和解答,如果有什么错误,还请指出,谢谢!2014大疆嵌入式笔试题试题编程基础1、有如下CAT_s结构体定义,回答:1)在一台64位的机器上,使用32位编译,Garfield变量占用多少内存空间?64位编译又是如何?(总分5分)2)使用32位编译情况下,给出一种判断所使用机器大小端的方法。

(总分5分)struct CAT_s{ int ld; char Color; unsigned short Age; char *Name; void(*Jump)(void);}Garfield;2、描述下面XXX这个宏的作用。

(总分10分)#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE*)0)->MEMBER)#defineXXX(ptr,type,member)({ const typeof(((type*)0)->member) *__mptr=(ptr); (type*)((char*)__mptr – offsetof(type,member));})3、简述C函数:1)参数如何传递(__cdecl调用方式);2)返回值如何传递;3)调用后如何返回到调用前的下一条指令执行。

(总分10分)4、在一个多任务嵌入式系统中,有一个CPU可直接寻址的32位寄存器REGn,地址为0x1F000010,编写一个安全的函数,将寄存器REGn的指定位反转(要求保持其他bit的值不变)。

(总分10分)5、有10000个正整数,每个数的取值范围均在1到1000之间,编程找出从小到大排在第3400(从0开始算起)的那个数,将此数的值返回,要求不使用排序实现。

单片机课后习题答案[1]1

单片机课后习题答案[1]1

单片机课后习题答案[1]1习题31.结合MCS-51系列单片机功能框图阐明其大致组成。

答:MCS-51系列单片机内部组成如图所示。

主要有8031、8051、875l 三种机型,基于HMOS 工艺,它们的指令系统与芯片引脚完全兼容,只是片内程序存储器(ROM, Read Only Memory)有所不同。

51子系列的主要功能为:●8位CPU;●片内带振荡器及时钟电路;●128B片内数据存储器;●4KB片内程序存储器(8031/80C31无);●程序存储器的寻址范围为64KB;●片外数据存储器的寻址范围为64KB;●21B特殊功能寄存器;●4×8根I/O线;●1个全双工串行I/O接口,可多机通信;●两个16位定时器/计数器;●中断系统有5个中断源,可编程为两个优先级;●111条指令,含乘法指令和除法指令;●布尔处理器;●使用单+5V电源。

2.综述80C51系列单片机各引脚的作用。

答:80C51 有4 个8 位并行I/O 口,共32 条端线:P0、P1、P2 和P3 口。

每一个I/O 口都能用作输入或输出。

用作输入时,均须先写入“1”;用作输出时,P0口应外接上拉电阻。

P0口的负载能力为8个LSTTL门电路;P1~P3口的负载能力为4个LSTTL门电路。

在并行扩展外存储器或I/O口情况下:P0口用于低8位地址总线和数据总线(分时传送)P2口用于高8位地址总线,P3口常用于第二功能,用户能使用的I/O口只有P1口和未用作第二功能的部分P3口端线。

3.80C51单片机内部包含哪些主要逻辑功能部件?各有什么主要功能?答:80C51 单片机内部包含含布尔(位)处理器的中央处理器、数据存储器和程序、并行输入/输出端口、中断系统、定时器/计数器,串行口、时钟电路、复位电路。

4.什么是ALU?简述MCS-51系列单片机ALU的功能与特点。

答:ALU是用于对数据进行算术运算和逻辑操作的执行部件,由加法器和其他逻辑电路(移位电路和判断电路等)组成。

C51单片机堆栈深入剖析

C51单片机堆栈深入剖析

51单片机堆栈深入剖析用C语言进行MCS51系列单片机程序设计是单片机开发和应用的必然趋势。

Keil公司的C51编译器支持经典8051和8051派生产品的版本,通称为Cx51。

应该说,Cx51是C语言在MCS51单片机上的扩展,既有C语言的共性,又有它自己的特点。

本文介绍的是Cx51程序设计时堆栈的计算方法。

1.堆栈的溢出问题。

MCS51系列单片机将堆栈设置在片内RAM中,由于片内RAM资源有限,堆栈区的范围也是有限的。

堆栈区留得太大,会减少其他数据的存放空间,留得太少则很容易溢出。

所谓堆栈溢出,是指在堆栈区已经满了的时候还要进行新的压栈操作,这时只好将压栈的内容存放到非堆栈区的特殊功能寄存器(SFR)中或者堆栈外的数据区中。

特殊功能寄存器的内容影响系统的状态,数据区的内容又很容易被程序修改,这样一来,之后进行出栈操作(如子程序返回)时内容已变样,程序也就乱套了。

因此,堆栈区必须留够,宁可大一些。

要在Cx51程序设计中防止堆栈的溢出,要解决两个问题:第一,精确计算系统分配给用户的堆栈大小,假设是M;第二,精确计算用户需要堆栈的大小,假设是N。

要求M≥N,下面分别分析这两个问题。

2.计算系统分配给用户的堆栈大小Cx51程序设计中,因为动态局部变量是长驻内存中的,实际上相当于局部静态变量,即使在函数调用结束时也不释放空间(这一点不同于标准C语言)。

Cx51编译器按照用户的设置,将所有的变量存放在片内和片外的RAM中。

片内变量分配好空间后,将剩下的空间全部作为堆栈空间,这个空间是最大可能的堆栈空间。

当然,因为Cx51是一种可以访问寄存器的C语言(特殊功能寄存器),因此可在程序中访问SP,将堆栈空间设置得小一点。

不过,一般没有人这么做。

本文只是讨论放在片内RAM的变量。

我们把变量分为两种情况:①用作函数的参数和函数返回值的局部变量。

这种变量尽量在寄存器组中存放。

为了讨论方便,假设统一用寄存器组0,具体的地址为0x00~0x07。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

本文由我司收集整编,推荐下载,如有疑问,请与我司联系
为什么调用者必须在cdecl 调用约定中清除堆栈?
为什么调用者必须在cdecl 调用约定中清除堆栈?[英]Why does the caller have to clear the stack in the cdecl calling convention? From: en.wikipedia/wiki/X86_calling_conventions
来自:http://en.wikipedia/wiki/X86_calling_conventions
push cpush bpush acall function_nameadd esp, 12 ;Stack clearingmov x, eax Why do
we need to explicitly add 12 to ESP to clear the stack since the called function should have
poped the parameters off the stack therefore restoring the stack pointer...?
为什么我们需要显式地将12 添加到ESP 以清除堆栈,因为被调用的函数应该将
参数从堆栈中取出,因此恢复堆栈指针...?
Another question:
另一个问题:
Theoretically, it would be possible to implement variable parameter functions with the callee taking care of the cleanup right (for instance if you pass the number of arguments on
the stack in a register)?
从理论上讲,可以实现变量参数函数,callee 负责清理权限(例如,如果你在寄
存器中传递堆栈中的参数数量)?
19
Because, with the C calling convention, the called function will not pop the parameters. That’s the point of this calling convention.
因为,使用C 调用约定,被调用的函数不会弹出参数。

这就是这个召唤惯例的重
点。

It allows things like variable arguments.
它允许像变量参数这样的东西。

6
It was right there on the wikipedia page above the _cdecl header。

相关文档
最新文档