精华内容
下载资源
问答
  • https://blog.csdn.net/zhuoya_/article/details/80516246

    https://blog.csdn.net/zhuoya_/article/details/80516246

    展开全文
  • 前言: ...接下来将解开函数调用的神秘面纱! 1 栈帧(Stack Frame) 从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数返回地址。每个函数的每次调用,都有它自己独立...

    前言:
    函数在编程中的重要性不言而喻。函数三要素,函数参数以及局部变量存在于栈上,局部变量定义需要初始化等,这些所谓的函数特征都熟背于心,时刻指导着我们设计函数。但这背后的原理是什么呢?底层的技术又是怎么实现?接下来将解开函数调用的神秘面纱!

    1 栈帧(Stack Frame)
    从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数返回地址。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。这里也说明了只含有局部变量(无全局或静态变量)的函数,是可重入的,因为每次调用的栈帧是独立的。
    在ARM处理器中,寄存器R11(fp)指向当前的栈帧的底部(高地址),寄存器R13(sp)指向当前的栈帧的顶部(低地址)。

    2 ARM指令集(Instruction-Set)
    汇编指令,粗略看来有两种,分别用于CPU内部的寄存器之间的数据传递以及寄存器和CPU外部的内存之间的数据传递,前者速度快于后者。
    也就是说,CPU处理数据的来源是寄存器,而寄存器的数据来源可以是寄存器,也可以是内存。也就有了不同的汇编指令:

    寄存器之间的数据传递(部分指令截取):
    在这里插入图片描述

    寄存器和内存之间的数据传递:
    STR(store register to a virtual address in memory):将寄存器中的数据存储到内存中
    LDR(load a single value from a virtual address in memory):将内存中的数据存储至寄存器

    3 函数调用过程的汇编代码分析
    有了前面1,2节的理解,就可以着手分析函数的汇编代码。
    编译器为: arm-none-eabi-gcc

    先看源文件(test.c)

    #include <stdio.h>
    #define MAX 100
    
    int fun_c(int p)
    {
    	return p -1;
    }
    
    int fun_a(int i,int j,int k,int l,int m,int n)
    {
    	int a = 10;
    	int ret = 0;
    	ret = a + i + j + k + l + m + n;
    	return ret;
    }
    
    int fun_b(int x,int y)
    {
    	int a = 20;
    	int max = MAX;
    	int q = fun_c(3);
    	return a - x - y - q;
    }
    
    int main(void)
    {
    	int c = 2;
    	int d = 4;
    	c = fun_a(1,2,3,4,5,6);
    	d = fun_b(7,8);
    	return 0;
    }
    

    使用arm-none-eabi-gcc -c test.c 编译test.c,生成test.o;
    再使用arm-none-eabi-objdump -d test.o 反汇编,生成汇编文件如下:

    Disassembly of section .text:
    
    00000000 <fun_c>:
       0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
       4:	e28db000 	add	fp, sp, #0
       8:	e24dd00c 	sub	sp, sp, #12
       c:	e50b0008 	str	r0, [fp, #-8]
      10:	e51b3008 	ldr	r3, [fp, #-8]
      14:	e2433001 	sub	r3, r3, #1
      18:	e1a00003 	mov	r0, r3
      1c:	e28bd000 	add	sp, fp, #0
      20:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
      24:	e12fff1e 	bx	lr
    
    00000028 <fun_a>:
      28:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
      2c:	e28db000 	add	fp, sp, #0
      30:	e24dd01c 	sub	sp, sp, #28
      34:	e50b0010 	str	r0, [fp, #-16]
      38:	e50b1014 	str	r1, [fp, #-20]	; 0xffffffec
      3c:	e50b2018 	str	r2, [fp, #-24]	; 0xffffffe8
      40:	e50b301c 	str	r3, [fp, #-28]	; 0xffffffe4
      44:	e3a0300a 	mov	r3, #10
      48:	e50b3008 	str	r3, [fp, #-8]
      4c:	e3a03000 	mov	r3, #0
      50:	e50b300c 	str	r3, [fp, #-12]
      54:	e51b2008 	ldr	r2, [fp, #-8]
      58:	e51b3010 	ldr	r3, [fp, #-16]
      5c:	e0822003 	add	r2, r2, r3
      60:	e51b3014 	ldr	r3, [fp, #-20]	; 0xffffffec
      64:	e0822003 	add	r2, r2, r3
      68:	e51b3018 	ldr	r3, [fp, #-24]	; 0xffffffe8
      6c:	e0822003 	add	r2, r2, r3
      70:	e51b301c 	ldr	r3, [fp, #-28]	; 0xffffffe4
      74:	e0822003 	add	r2, r2, r3
      78:	e59b3004 	ldr	r3, [fp, #4]
      7c:	e0822003 	add	r2, r2, r3
      80:	e59b3008 	ldr	r3, [fp, #8]
      84:	e0823003 	add	r3, r2, r3
      88:	e50b300c 	str	r3, [fp, #-12]
      8c:	e51b300c 	ldr	r3, [fp, #-12]
      90:	e1a00003 	mov	r0, r3
      94:	e28bd000 	add	sp, fp, #0
      98:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
      9c:	e12fff1e 	bx	lr
    
    000000a0 <fun_b>:
      a0:	e92d4800 	push	{fp, lr}
      a4:	e28db004 	add	fp, sp, #4
      a8:	e24dd018 	sub	sp, sp, #24
      ac:	e50b0018 	str	r0, [fp, #-24]	; 0xffffffe8
      b0:	e50b101c 	str	r1, [fp, #-28]	; 0xffffffe4
      b4:	e3a03014 	mov	r3, #20
      b8:	e50b3008 	str	r3, [fp, #-8]
      bc:	e3a03064 	mov	r3, #100	; 0x64
      c0:	e50b300c 	str	r3, [fp, #-12]
      c4:	e3a00003 	mov	r0, #3
      c8:	ebfffffe 	bl	0 <fun_c>
      cc:	e50b0010 	str	r0, [fp, #-16]
      d0:	e51b2008 	ldr	r2, [fp, #-8]
      d4:	e51b3018 	ldr	r3, [fp, #-24]	; 0xffffffe8
      d8:	e0422003 	sub	r2, r2, r3
      dc:	e51b301c 	ldr	r3, [fp, #-28]	; 0xffffffe4
      e0:	e0422003 	sub	r2, r2, r3
      e4:	e51b3010 	ldr	r3, [fp, #-16]
      e8:	e0423003 	sub	r3, r2, r3
      ec:	e1a00003 	mov	r0, r3
      f0:	e24bd004 	sub	sp, fp, #4
      f4:	e8bd4800 	pop	{fp, lr}
      f8:	e12fff1e 	bx	lr
    
    000000fc <main>:
      fc:	e92d4800 	push	{fp, lr}
     100:	e28db004 	add	fp, sp, #4
     104:	e24dd010 	sub	sp, sp, #16
     108:	e3a03002 	mov	r3, #2
     10c:	e50b3008 	str	r3, [fp, #-8]
     110:	e3a03004 	mov	r3, #4
     114:	e50b300c 	str	r3, [fp, #-12]
     118:	e3a03006 	mov	r3, #6
     11c:	e58d3004 	str	r3, [sp, #4]
     120:	e3a03005 	mov	r3, #5
     124:	e58d3000 	str	r3, [sp]
     128:	e3a03004 	mov	r3, #4
     12c:	e3a02003 	mov	r2, #3
     130:	e3a01002 	mov	r1, #2
     134:	e3a00001 	mov	r0, #1
     138:	ebfffffe 	bl	28 <fun_a>
     13c:	e50b0008 	str	r0, [fp, #-8]
     140:	e3a01008 	mov	r1, #8
     144:	e3a00007 	mov	r0, #7
     148:	ebfffffe 	bl	a0 <fun_b>
     14c:	e50b000c 	str	r0, [fp, #-12]
     150:	e3a03000 	mov	r3, #0
     154:	e1a00003 	mov	r0, r3
     158:	e24bd004 	sub	sp, fp, #4
     15c:	e8bd4800 	pop	{fp, lr}
     160:	e12fff1e 	bx	lr
    
    

    汇编说明:
    140: e3a01008 mov r1, #8

    140 表示该条指令的偏移地址,用于CPU通过PC取址;
    e3a01008 表示mov r1, #8 这条汇编指令翻译成arm指令集(Instruction-Set)的机器码;
    mov r1, #8 表示 将立即数8复制给r1寄存器。

    调用过程:
    1 main函数的栈帧中,首先将栈底指针fp,返回地址lr入栈,再开辟16字节的栈空间;
    2 局部变量c/d入栈;
    3 调用fun_a所用到的参数6/5入栈 ();
    4 fun_a函数的栈帧中,首先将栈底指针fp入栈,再开辟28字节的栈空间;
    5 4个寄存器(r0–r3)中的值入栈,通过栈底指针fp+4,fp+8得到剩余的两个值(),运算后入栈;
    6 将结果存入r0寄存器,sp指向fp,释放栈空间,fp出栈。
    7 同理,调用fun_b函数。

    注意:
    1 被调函数参数多于4个的才会入调用函数的栈帧,少于等于4个的直接通过r0–r3传递;
    2 被调函数栈帧中的fp是调用函数的sp,通过 fp+偏移量 可以访问到调用函数的栈帧;
    3 当fun_a函数返回后,栈帧空间被释放,但其中的值依然存在,当继续运行至fun_b函数时,其栈帧同样使用刚刚fun_a的,只不过地址中的值为fun_a留下的,所以局部变量需要初始化,原因并不是编译器乱分配的,而是上次使用者产生的垃圾数据。
    4 多次递归调用的函数一直处于入栈,有可能会导致栈溢出(Stack Overflow)。

    5 尽量使用地址传递:原因如下
    值传递,相当于一个变量即存在于被调函数栈帧中,又会同样存在于调用函数栈帧中,在被调函数栈帧中修改该值,并不会影响调用函数栈帧中的值,因为地址不一样。可以理解为copy了一份;
    地址传递,相当于一个变量即存在于调用函数栈帧中,而在被调用函数栈帧中存放的是该变量的地址,同一个地址,被调函数可以修改该值。

    4 总结
    在嵌入式C编程中(rom/ram资源有限),函数的参数应尽量不要超过4个,以提高执行效率。最好的方法是将多个参数打包成结构体,并且在传递参数时传递结构体指针,这样的效率最高,因为传递参数只用寄存器参与,并且在被调函数的栈帧中只用压栈传入的地址,而不是压栈全部参数,减少了栈的空间使用。
    函数调用嵌套层次不要太深,避免使用递归调用,以免出现堆栈溢出(Stack Overflow)。

    展开全文
  • C++ 函数调用过程变化解析

    千次阅读 2018-10-04 15:55:08
    函数调用的另一个词语表示叫作 过程。一个过程调用包括将数据和控制从代码的一部分传递到另一部分。 另外,它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。 而大多数机器的数据传递、局部...

    “ 走好选择的路,别选择好走的路,你才能拥有真正的自己。”

     

    There you go again!

    I'll back you up!

     

    记录下函数调用的情况~

     

    函数调用的另一个词语表示叫作 过程。一个过程调用包括将数据和控制从代码的一部分传递到另一部分。

    另外,它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。

    而大多数机器的数据传递、局部变量的分配和释放通过操纵程序栈来实现。为单个过程(函数调用)分配的那部分栈称为栈帧

    需要明确的是,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

    先来了解一个概念,栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。栈帧其实是两个指针寄存器,寄存器%ebp为帧指针(指向该栈帧的最底部),而寄存器%esp为栈指针(指向该栈帧的最顶部),当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的,换句话说,就是如果该栈存在,%ebp帧指针是不移动的,访问栈里面的元素可以用-4(%ebp)或者8(%ebp)访问%ebp指针下面或者上面的元素)。

    总之一句话,栈帧的主要作用是用来控制和保存一个过程的所有信息的。

    例:假设过程P(调用者)调用过程Q(被调用者),则Q的参数放在P的栈帧中。另外,当P调用Q时,P中的返回地址被压入栈中,形成P的栈帧的末尾 (返回地址就是当程序从Q返回时应该继续执行的地方)。Q的栈帧从保存的帧指针的值开始,后面到新的栈指针之间就是该过程的部分了。

    那到底是怎样个过程呢?

     

    入栈操作:push eax; 等价于 esp=esp-4,eax->[esp];如下图

    出栈操作:pop eax; 等价于 [esp]->eax,esp=esp+4;如下图

    我们来看下面这个C程序在执行过程中,栈的变化情况

    void func(int m, int n) {

        int a, b;

        a = m;

        b = n;

    }

    main() {

    ...

        func(m, n);

    L:  下一条语句

    ...

     

    在main调用func函数前,栈的情况,也就是说main的栈帧:

    从低地址esp到高地址ebp的这块区域,就是当前main函数的栈帧。当main中调用func时,写成汇编大致是:

    push m

    push n; 两个参数压入栈

    call func; 调用func,将返回地址填入栈,并跳转到func

    当跳转到了func,来看看func的汇编大致的样子:

    __func:

            push ebp; 这个很重要,因为现在到了一个新的函数,也就是说要有自己的栈帧了,那么,必须把上面的函数main的栈帧底部保存起                        ; 来,栈顶是不用保存的,因为上一个栈帧的顶部讲会是func的栈帧底部。(两栈帧相邻的)

            mov ebp, esp; 上一栈帧的顶部,就是这个栈帧的底部

            ;暂时先看现在的栈的情况

                     ;到这里,新的栈帧开始了

                     sub esp, 8   ;  int a, b 这里声明了两个int,所以esp减小8个字节来为a,b分配空间

                     mov dword ptr [esp+4], [ebp+12];   a=m

                     mov dword ptr [esp], [ebp+8]; b=n         

       这样,栈的情况变为:

                        ret 8     ;  返回,然后8是什么意思呢,就是参数占用的字节数,当返回后,esp-8,释放参数m,n的空间

     

    由此可见,通过ebp,能够很容易定位到上面的参数。当从func函数返回时,首先esp移动到栈帧底部(即释放局部变量),然后把上一个函数的栈帧底部指针弹出到ebp,再弹出返回地址到cs:ip上,esp继续移动划过参数,这样,ebp,esp就回到了调用函数前的状态,即现在恢复了原来的main的栈帧。

     

    好的,加油!

    展开全文
  • 函数调用的时候会内存分配给函数一段内存用于存放数据。 函数初次调用分配 1 ESP指针指向栈顶 EBP指向底,底属于高位地址,每次存入数据由栈顶来进行储存。 2 调用函数,EIP设定前一个函数结束,ESP所指地址...

    在函数调用的时候会内存分配给函数一段栈内存用于存放数据。

    函数初次调用分配

    1 ESP指针指向栈顶 EBP指向栈底,栈底属于高位地址,每次存入数据由栈顶来进行储存。
    2 调用函数,EIP设定前一个函数结束,ESP所指地址存入EBP位置,此时ESP=EBP
    3 ESP自动分配空间 ESP-0xch位置,同时进行下一次的存储。此时函数调用成功。

    函数调用退栈

    1 ESP=EBP 此时调用下一个函数,先还原EBP,之后读取EIP说明此函数进行调用。
    2 ESP进行增位操作,自此循环进行调用。
    图文详解:https://blog.csdn.net/xi_niuniu/article/details/44978207#commentBox

    展开全文
  • 小例子一步一步解释“函数调用过程中变化过程” 1 问题描述  在此之前,我对C中函数调用过程中的变化,仅限于了解有好几种参数的入栈顺序,其中的按照形参逆序入栈是比较常见的,也仅限于了解到...
  • c语言中函数调用利用ebp esp寄存器建立,通过的嵌套形成了调用的进入和退出,《深入理解计算机系统》对此有详细的讨论,本次linux内核分析mooc的学习对此亦进行了深入的讨论,本文结合汇编代码,一码一图对的...
  • 函数调用栈 剖析+图解
  • 函数调用过程中函数详解

    万次阅读 多人点赞 2018-08-14 16:19:51
    当进程被加载到内存时,会被分成很多段 代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写,如果发生写操作则会提示segmentation fault ... (Stack):存放局部变量,函数参数,当前状...
  • Linux函数调用

    千次阅读 2017-06-05 17:31:36
    栈与函数调用惯例(又称调用约定)— 基础篇 记得一年半前参加百度的校招面试时,被问到函数调用惯例的问题。...最近看书过程中,重新回顾了这些知识点,对整个调用栈又有了较深入的理解。作为笔记,记录于此。
  • 函数调用过程栈帧变化详解

    千次阅读 2015-12-17 15:38:42
    函数调用过程栈帧变化详解 函数调用另一个词语表示叫作 过程。一个过程调用包括将数据和控制从代码的一部分传递到另一部分。另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间。而数据...
  • C语言 函数调用栈变化 (VC6.0)
  • 总是被问及函数调用过程中堆栈的变化以及回溯的原理,最近好好的研究了一下函数调用过程中各个寄存器和堆栈状态的变化,在这边分享一下,如果有不对的地方,希望得到指正,本文使用VS2008工具运行下面这段代码执行...
  • 通过分析函数调用过程的堆栈变化,可以看出在被调函数的EBP寄存器地址存放的是调用函数的EBP寄存器地址,EBP地址+4存放的是函数调用完成后的下一条指令存放地址,该指令的前一条指令则是调用函数的指令。说起来有点...
  • 通过分析函数调用过程的堆栈变化,可以看出在被调函数的EBP寄存器地址存放的是调用函数的EBP寄存器地址,EBP地址+4存放的是函数调用完成后的下一条指令存放地址,该指令的前一条指令则是调用函数的指令。说起来有点...
  • 浅析函数调用栈

    千次阅读 多人点赞 2015-07-25 15:47:30
    1. 预备知识:函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果,还有大家比较熟悉的一句话,就是函数调用是在上发生的,那么在计算机内部到底是如何...
  • 溢出笔记1.1 函数调用过程

    千次阅读 2016-04-09 23:04:35
    区随着函数调用动态变化,每个函数调用时在上占用的空间称为栈帧。用一个示例来说明上保存的内容及动态变化过程。 下面是一个程序,生成一个对话框显示一条“Hello World!”消息。下面是该程序的C代码: ...
  • C/C++函数调用过程--函数(二)

    千次阅读 2015-04-23 17:18:24
    上一篇比较简单的理解函数调用时的参数进出,这里转载一篇别人的文章,通过汇编比较完整的讲解进出过程。看是看觉得略难理解,但仔细看完很有收获。 函数调用--函数 函数调用大家都不陌生,调用者向被...
  • 函数调用过程

    万次阅读 多人点赞 2018-01-31 09:21:19
    今天突然看到有人私信我说一直没写函数调用过程(栈帧的形成和销毁过程)这篇博文,赶紧补上。 刚看的栈帧内容时,我很迷惑,我觉得栈帧创建和销毁很麻烦,几句话根本说不完,而且我好像描述不清楚他的过程,所以...
  • 转自:函数调用栈 剖析+图解   栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈...
  • 函数调用--函数

    2015-08-31 17:46:25
    转载自函数调用--函数 函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果。还有大家比较熟悉的一句话,就是函数调用是在上发生的,那么在计算机...
  • 函数调用过程中的栈帧结构及其变化

    千次阅读 多人点赞 2018-04-28 02:34:42
    前言:本文旨在从汇编代码的角度出发,分析函数调用过程中栈帧的变化。栈帧的简单介绍: 当某个函数运行时,机器需要分配一定的内存去进行函数内的各种操作,这个过程中分配的那部分称为栈帧。下图描述了栈帧的...
  • 函数调用、堆

    千次阅读 2014-10-22 09:31:35
    在执行函数时,函数内局部变量的存储单元都在上创建,函数执行结束时这些存储单元自动被释放。内存分配运算内置于处理器的指令集中,一般使用寄存器来存取,效率很高,但是分配的内存容量有限。 2) 从堆上分配...
  • 显示函数调用栈

    千次阅读 2015-07-17 10:52:20
    本文讲解如何在调试器中显示函数调用栈,如下图所示:   原理 首先我们来看一下显示调用栈所依据的原理。每个线程都有一个栈结构,用来记录函数的调用过程,这个栈是由高地址向低地址增长的,即栈底的地址比栈顶...
  • 调用函数时,变化

    千次阅读 2018-05-30 09:35:17
    每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地址地)。下图为典型的存取器安排,观察在其中的...
  • 函数调用时的空间变化

    千次阅读 2015-04-10 13:13:19
    函数调用栈空间变化 我们已经基本了解了,接下来再看下,函数退出时栈的变化 ESP-0xC 此时ESP和EBP指向相同地址,当前地址内存中存放的是前一个函数的EBP地址 恢复原来EBP的数值 ...
  • 函数调用过程看栈帧的变化
  • 函数调用过程理解

    千次阅读 2018-05-07 20:44:50
    栈除了先入后出(FILO)外还有以下特性:每一个进程在用户态对应一个调用栈结构(call stack) 程序中每一个未完成运行的函数对应一个栈帧(stack frame),栈帧中保存函数局部变量、传递给被调函数的参数等信息 栈底...
  • C语言函数调用时候内存中的动态变化详细分析

    千次阅读 多人点赞 2019-05-02 00:23:41
    先了解如下几点知识和过程: *冯诺伊曼体系计算机程序指令代码都是提前从硬盘加载进入内存从而执行的(如果是哈佛体系结构的计算机指令代码是直接在外存里面执行的,具体可以看我这篇文章,计算机冯诺伊曼体系结构...
  • 函数调用栈分析

    千次阅读 2011-10-04 21:04:06
    而要深入理解函数调用栈,最重要的两点就是:栈的结构变化,ebp寄存器的作用。 首先要认识到这样两个事实: 1. 一个函数调用动作可分解为:零到多个push指令(用于参数入栈),一个call指令。call指令内部其实还...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 108,811
精华内容 43,524
关键字:

函数调用过程栈的变化