精华内容
下载资源
问答
  • 函数调用堆栈过程 调用约定 函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是...
        

    函数调用堆栈过程

    调用约定

    函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。
    主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是调用者还是被调用者。另外来说不同的函数调用约定函数产生的符号名称不同

    举个栗子,对于cdecl,参数是从右到左传递,堆栈平衡是由调用函数来执行的;而win32API一般使用的是stdcall,参数同样是采用了从右往左传递,而函数的堆栈平衡则是由被调用函数执行(不支持可变参数);fastcall参数直接放入寄存器而非栈中,规定前两个参数分别放入ecx和edx中,当寄存器用完时候参数才按照从右往左的顺序压入堆栈。

    调用约定 使用场景
    _cdecl c调用约定
    _stdcall windows标准调用约定
    _fastcall 快速调用约定
    _thiscall C++成员函数调用约定

    压栈过程

    int add(int a, int b)
    {
        return a+b;
    }
    
    int main()
    {
        int a = 1;
        int b = 2;
        int res = add(a,b);
        return 0;
    }
    1. 首先从main函数初始,ebp和esp分别存放函数的栈底地址和栈顶地址,此时ebp-4即是a,ebp-8则是b的地址。
    2. 然后调用函数add,第一先将参数从右往左依次入栈,push在调用方的函数栈当中,也就是说此时esp往里开辟了两个参数
      图片描述
    3. 执行call指令,首先将下一条指令地址进行入栈,
      图片描述
    4. 随后开辟新栈,进行现场保护
      图片描述

      • 这里省略了现场保护的过程,主要做的就是push了多个寄存器(例如edi,ebx,edi)的值,在完成后还原现场进行pop,对程序没有什么其他影响这里省略。
      • 将edi置为栈顶地址,ecx置为11h,eax置为0CCCCCCCCh。
      • 从edi开始循环拷贝eax,也就是将整个栈内初始化为0CCCCCCCCh,也就是常见的“烫”。
    5. 执行add函数
      开辟一个临时变量,值是(a)(ebp+8) + (b)(ebp+0Ch),将这个值放入eax中。
    6. 执行完成后回退栈针
      图片描述

      • 首先mov esp,ebp 将add的函数栈回退。
      • 随后pop,将[ebp]的值弹出给ebp,也就是ebp弹回退到main函数的栈底。
      • 执行ret指令,将下一条指令的地址弹出给eip寄存器。
      • 随后在main函数的函数栈中回退形参变量所占用的内存 add esp+8。

    那么再来看看其他情况

    上面的返回值是一个int类型,也就是C的内置类型,通过eax寄存器带出。

    如果是一个double或者long long呢?那么可以通过eax、edx两个寄存器带出。

    如果是一个自定义类型呢?其实也是类似的:

    • 首先在参数传参过程中不能直接去push一个寄存器了,而现在是通过开辟内存后,将自定义类型的实参b的地址放入esi中,循环赋给实参。
      例如说自定义类型的b参数
      图片描述
    • 参数传递完成之后,再来看看返回值,返回值首先会在压入所有的形参之后,将main函数中返回值(临时量)的地址压入参数。
      图片描述
    • 对返回值的操作也是类似的,通过esi和edi、ecx,循环拷贝到main函数的函数栈之中。
    • 临时量返回值的地址是最后才会压栈的,那么它的地址一定是ebx+8
    展开全文
  • C语言代码如下 #include <stdio.h> int plus(int x,int y){ return x+y; } int main() { printf("hello"); plus(1,2); return 0; } 我们从调用plus开始分析 ESP表示栈顶 EBP表示栈底 EIP表示下个指令的...

    C语言代码如下

    #include <stdio.h>
    int plus(int x,int y){
    	return x+y;
    }
    int main()
    {
    	printf("hello");
    	plus(1,2);
    	return 0;
    }
    

    我们从调用plus开始分析
    ESP表示栈顶
    EBP表示栈底
    EIP表示下个指令的地址

    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述 在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述全部写入CC的目的是让程序可以中断
    比如这些栈的空间你没用完,接下来就执行CC中断了

    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

    展开全文
  • ebp栈底指针(寄存器)esp栈顶指针(寄存器)栈底指针(ebp)-偏移表示地址1、函数调用先压实参(从右向左支持可变参),通过(栈底指针-偏移)把实参移到寄存器(eax、ecx)再压入栈2、(call指令)先把当前栈帧下...

    ebp栈底指针(寄存器)esp栈顶指针(寄存器)

    栈底指针(ebp-偏移表示地址

    1、函数调用先压实参(从右向左支持可变参),通过(栈底指针-偏移)把实参移到寄存器(eaxecx)再压入栈

    2、call指令)先把当前栈帧下一行指令地址入栈,再跳转。

     入栈过程:

    1、把调用方栈底指针(ebp)压入当前栈顶,再把调用方栈底指针(ebp)向当前栈顶指针(esp);
    
    2、栈顶指针上移(esp-X)开辟空间;寄存器(ebx、esi、edi)入栈;把(esp)放入(edi)中;把X/4放入(ecx)中;把(0xCCCCCCCC)放入(eax)中;
    
    3、Rep stor循环使用(0xCCCCCCC)初始化开辟空间

    总结:

    1、把当调用方栈底指针入栈作为当前栈底,并把主调方栈顶指向当前栈底;

    2、为当前栈帧开辟空间,保存响应寄存器;

    2、循环初始化已开辟的空间。

    回退过程:

    1、栈顶指针(esp)回退到当前栈底(ebp);
    
    2、(pop ebp)栈顶出栈把值赋给栈底指针(ebp),即ebp指向调用方栈底;
    
    3、ret指令 栈顶(call指令的下一行地址)出栈放入PC寄存器

    汇编片段:

    Push ebp //把调用方栈底指针(ebp)压入当前栈顶
    
    Move ebp,esp //esp值赋给ebp即ebp拉倒栈顶
    
    Sub esp,4CH //为当前函数开辟栈帧
    
    //Ebx、esi、edi
    
    Rep stos ebp-esp 0xCCCCCCC //初始化栈帧
     
    
    Move esp、ebp //ebp值赋给esp即esp拉回栈底
    
    Pop ebp //栈顶元素出栈把值赋给ebp,即ebp指向调用方栈底
    
    Ret //栈顶(call指令的下一行地址)出栈放入PC寄存器

    展开全文
  • ``` #include void reverse(int i); int main() { reverse(1); } void reverse(int i) { if (i > 5) ...第一个为什么会堆栈溢出,不是有i++么? 第二个为什么又可以?
  • 数据结构:堆栈的实现 看了一篇CSDN的一篇有关实现堆栈的文章...// C++环境下C语言实现堆栈 #include <iostream>//C++标准库 #include <cstdlib>//调用malloc和free函数所需要的头文件 using namespa

    数据结构:堆栈的实现

    看了一篇CSDN的一篇有关实现堆栈的文章(https://blog.csdn.net/weixin_43914857/article/details/108420153),决定使用VS平台实践一下堆栈,修改了一些其中错误的地方(我是一名初学者不对之处望请指正)。

    // C++环境下C语言实现堆栈
    #include <iostream>//C++标准库
    #include <cstdlib>//调用malloc和free函数所需要的头文件
    using namespace std;//使用C++环境不可或缺之处
    typedef struct Stack 
    {
        int* base;//本堆栈默认存储int型数据
        size_t capcity;//堆栈最大容量
        size_t size;//堆栈已存数据个数,size_t定义为unsigned int
    }Stack;
    int stack_init(Stack* s, size_t cap) //堆栈初始化(创建)
    {
        s->base = (int*)malloc(sizeof(int) * cap);
        //原文章中此处是Sizeof(Stack)觉得有所不妥
        if (s->base == NULL) return -1;
        //检测内存是否溢出
        s->capcity = cap;
        s->size = 0;
        //初始存入个数为0
        return 0;
    }
    void stack_destory(Stack* s) //堆栈销毁
    {
        free(s->base);
        //保持malloc了就一定要free的好习惯
        cout << "Waring:Stack has been Destoried" << endl;
    }
    void stack_clear(Stack* s) //堆栈清空
    {
        s->size = 0;
        //很暴力的清空方法
    }
    int stack_empty(Stack* s) //堆栈是否为空,空为1,非空为0
    {
        return !(s->size);
        //原文此处为return s->size;逻辑为空为0与后面的逻辑冲突
    }
    int stack_full(Stack* s) //堆栈是否为满,满为1,非满为0
    {
        return s->capcity == s->size;
    }
    void stack_push(Stack* s, int data) //入栈操作
    {
        if (!stack_full(s))
        {
            *(s->base + (s->size)++) = data;
            //先存入然后再s->size加一
        }
        else cout << "Error:Stack is Full" << endl;
        //加一点提示比较好
    }
    int stack_top(Stack* s) //获取栈顶元素
    {
        if (!stack_empty(s)) 
        {
            return *(s->base + s->size - 1);
            //原文此处未-1,存在错误
        }
        cout << "Error:Stack is Empty" << endl;
        return 0;
    }
    int stack_pop(Stack* s) //出栈操作
    {
        if (!stack_empty(s))//此处为原文判断逻辑,很明显应采取空为1,非空为0
        {
            return *(s->base + --(s->size));
            //先s->size减一在计算
        }
        cout << "Error:Stack is Empty" << endl;
        return 0;
    }
    

    测试程序

    // 建立一个堆栈然后依次入栈,出栈,实现数列倒序排列
    int main()
    {
        Stack S;
        Stack *S1=&S;//指针必须被指向一个确定的内容,若删除此行则需要将S1换为&S
        int i,j;
        if (stack_init(S1, 4) == -1)
        {
            cout << "Error:Insufficient Memory" << endl;
            return 0;
        }
        stack_push(S1, 4);//测试一般的入栈功能
        stack_push(S1, 3);
        stack_push(S1, 2);
        stack_push(S1, 1);
        stack_push(S1, 1);//测试溢出上限时入栈报错
        
        j = stack_top(S1);//测试栈顶元素功能
        cout <<"Stack Top:"<< j << endl;
        for (i = 0; i < 5; i++)//测试出栈功能和栈空出栈报错
        {
            j = stack_pop(S1);
            cout << j << endl;
        }
       
        stack_destory(S1);//测试销毁堆栈功能
        return 0;
    }
    
    展开全文
  • c语言堆栈溢出问题

    2020-12-05 17:21:42
    1.函数的调用,系统所作的工作 2.函数调用时与内存管理 3.递归函数 4.数组作为形参调用函数时,为什么需要连同数组长度一起传进来
  • 文章来源:https://blog.seclibs.com/函数调用堆栈图-c语言/ 我们就使用一个简单的c语言程序来对描述一下在函数调用的时候都发生了什么。 中间的一小段没有意义的汇编语言是为了方便设置断点,为后面的调试做好铺垫...
  • c语言堆栈认识汇总

    2012-03-10 22:23:40
    2.为什么c语言在执行工作时程序将使用一个运行时堆栈在中国一些老师或一些低劣质量的书,喜欢把栈叫堆栈。其实堆,栈是栈。c语言在执行工作时程序将使用一个运行时堆栈,其实C语言是基于过程的语言,又叫基于函数的...
  • C语言堆栈问题

    2012-07-09 19:50:34
    堆栈这些结构的时候,先说说程序中的内存段: .data - 已初始化全局/静态变量,在整个软件执行过程中有效; .bss - 未初始化全局/静态变量,在整个软件执行过程中有效; .stack - 函数调用栈,其中的内容在函数...
  • 分类专栏: C/C++ 文章标签: C语言 调用堆栈 程序的执行过程 版权 以前接触程序时,只知道程序写的对,一般都能运行出来,但是却不知道程序是怎么一步一步将每一步编译链接起来的,今天我们用汇编来看一下程序到底...
  • 返回调用堆栈 buffer:提供一个指针的数组 size:指定缓冲区的个数,即设置的调用深度 int: 返回实际返回的调用深度 每个地址指针由 函数名、地址偏移、返回地址组成 char **backtrace_symbols(void *const *...
  • C语言函数调用堆栈知识

    千次阅读 2016-05-30 21:09:19
    C语言的程序运行可以说就是不断的调用函数,从主入口的main函数到各种各样的库函数,再到用户自定义的完成特定功能的函数。 程序中关于一个函数的操作主要包括三个方面。①函数声明,②函数定义,③函数调用。...
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 ... 本篇来分析函数调用的过程:通过下面一个简单的例子来进入话题:#include&amp;lt;stdio.h&amp;g...
  • 1、函数调用层次太深。 函数递归调用时,系统要在栈中不断...由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢...
  • 1 // 这段代码显示,在C语言修改函数的返回地址 2 int test1() 3 { 4 return 0; 5 } 6 7 int test2(int a) 8 { 9 *(&a-1) = (int)test1; // 将返回地址修改为test1 10 return a; 11 } 12 ...
  • 这几天为了用编程的方式获取当前的调用堆栈信息,在网上进行了一番大查找。发现在Linux平台上获取当前调用堆栈的信息相对容易。但在windows平台上获取当前调用堆栈的信息就不是那么容易了。 网上有不少介绍windows...
  • 前面的文章《Windows上获取当前调用堆栈信息,StackWalker的C语言实现》实现了如何通过编程的方式获取调用堆栈的详细信息。本文接下说明如何将分析得到的结果用图形化的方式展现出来。为此选用了功能强大的文本处理...
  • C语言调用方式

    2012-08-26 20:12:43
    __stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。 2. __cdecl调用 __cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护...
  • 最近,看了很多关于uboot的分析,其中就有说要为C语言的运行,就要准备好堆栈。而在Uboot的start.S汇编代码中,关于系统初始化,也看到有堆栈指针初始化这个动作。但是,从来只是看到有人说系统...为何C语言的函数调用
  • 1、函数参数的代入 4字节 push 入栈 入栈顺序 从右向左 1字节 push 入栈 8字节 push 入栈 ...1、先将调用方的栈底地址入账 2、让ebp=esp 3、esp-栈帧大小 4、将栈帧内存写成0xcccc cccc 3、函...
  • 文章目录一、内存分区及栈、堆区的使用内存分区栈区的使用堆区的使用二、static与extern的使用三、C语言中的伪常量const四、宏函数及函数调用模型 一、内存分区及栈、堆区的使用 内存分区     &...
  • C语言堆栈模拟队列

    2020-05-17 11:39:04
    所谓用堆栈模拟队列,实际上就是通过调用堆栈的下列操作函数: int IsFull(Stack S):判断堆栈S是否已满,返回1或0; int IsEmpty (Stack S ):判断堆栈S是否为空,返回1或0; void Push(Stack S, ElementType item )...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,047
精华内容 418
关键字:

c语言调用堆栈

c语言 订阅