当前位置: 首页 > article >正文

菜鸟的程序编程理解

文章目录

  • 一、局部函数如何调用-- 及-- 调用前后栈上动态分配过程
    • 1. 调用 `add` 函数前的准备(参数传递和返回地址保存)
    • 2. `add` 函数栈帧创建
    • 3. `add` 函数执行
    • 4. `add` 函数栈帧销毁
    • 5.备注(栈帧和基址指针的理解)

一、局部函数如何调用-- 及-- 调用前后栈上动态分配过程

下面详细说明从 add 函数栈帧创建到销毁过程中,栈上依次入栈的参数及相关信息,结合之前给出的示例代码:

#include <stdio.h>

// 被调函数
int add(int a, int b) {
    int result = a + b;
    return result;
}

// 主调函数
int main() {
    int x = 3;
    int y = 5;
    int sum = add(x, y);
    printf("The sum is: %d\n", sum);
    return 0;
}

1. 调用 add 函数前的准备(参数传递和返回地址保存)

main 函数调用 add 函数时,首先要进行参数传递和返回地址的保存,这些操作会使数据入栈,具体如下:

  • 参数入栈:按照从右到左的顺序将参数压入栈中。在这个例子中,先将参数 b(也就是 y 的值 5)压入栈,接着将参数 a(即 x 的值 3)压入栈。这是因为在C语言的函数调用中,通常采用从右到左的参数传递顺序,这样可以方便实现可变参数函数。
  • 返回地址入栈:把 main 函数中调用 add 函数之后要执行的下一条指令的地址压入栈。这是为了让 add 函数执行完毕后能够准确返回到 main 函数的正确位置继续执行。

2. add 函数栈帧创建

当参数和返回地址都入栈后,就开始为 add 函数创建栈帧,栈帧里包含以下入栈内容:

  • 旧的基址指针(Base Pointer,BP)main 函数栈帧的基址指针(通常是 ebp 寄存器的值)会被压入栈。这是为了在 add 函数执行结束后,能够恢复 main 函数的栈帧。基址指针指向当前栈帧的底部,它可以帮助函数访问栈上的参数和局部变量。
  • 新的基址指针设置:将当前栈指针(通常是 esp 寄存器的值)赋给基址指针寄存器(ebp),这样就确定了 add 函数栈帧的底部。
  • 局部变量入栈:为 add 函数的局部变量分配栈空间。在这个例子中,add 函数有一个局部变量 result,会在栈上为其分配空间。

3. add 函数执行

add 函数执行期间,栈上的数据基本保持不变,只是利用栈上的参数和局部变量进行计算。add 函数从栈上获取参数 ab 的值,计算它们的和,并将结果存储在局部变量 result 中。

4. add 函数栈帧销毁

add 函数执行完毕准备返回时,会进行栈帧销毁操作,涉及数据出栈,不过从入栈角度来看,在销毁前没有新的数据入栈,而是按照相反的顺序将之前入栈的数据依次出栈:

  • 恢复旧的基址指针:从栈中弹出之前保存的 main 函数的基址指针值,恢复到 ebp 寄存器,这样就回到了 main 函数的栈帧底部。
  • 释放局部变量空间:通过调整栈指针(增加 esp 的值)来释放 add 函数局部变量所占用的栈空间。
  • 返回地址出栈:从栈中弹出之前保存的返回地址,将控制权交还给 main 函数,让 main 函数从该地址处继续执行。
  • 参数出栈:可以由调用者(main 函数)或者被调用者(add 函数)来清理栈上的参数,这取决于具体的调用约定。在常见的C调用约定(如 __cdecl)中,由调用者负责清理栈上的参数,也就是在 main 函数中调整栈指针来移除之前压入的参数 ab

综上所述,从 add 函数栈帧创建到销毁,栈上依次入栈的内容为:参数 b、参数 a、返回地址、旧的基址指针、局部变量 result

5.备注(栈帧和基址指针的理解)

5.1基址指针
基址指针(Base Pointer)是计算机系统中用于辅助管理栈帧的数据指针,在 x86 架构中,通常用 ebp(Extended Base Pointer)寄存器来表示。下面详细介绍它的作用和用途:

定义与存储位置
在 x86 架构的计算机中,ebp 寄存器是一个 32 位的通用寄存器,在 64 位系统里对应为 rbp 寄存器。它在栈帧管理中起到关键作用,一般存储着当前栈帧的基地址,也就是栈帧的起始位置。

具体作用

1. 定位栈帧
在函数调用过程中,每个函数都会在栈上分配一块独立的内存区域,即栈帧。基址指针指向当前函数栈帧的底部,这样可以清晰界定当前函数栈帧的范围,把不同函数的栈帧区分开来。当函数调用嵌套时,不同函数的栈帧依次排列在栈上,基址指针能够帮助操作系统和程序准确找到当前正在执行的函数的栈帧。

2. 访问局部变量
函数的局部变量通常存储在栈帧中,基址指针可以作为访问这些局部变量的参考点。通过相对于基址指针的偏移量,程序可以准确地找到栈帧内的各个局部变量。例如,若某个局部变量位于基址指针偏移量为 -4 字节的位置,程序就可以通过 ebp - 4 来访问该变量。

3. 访问函数参数
在函数调用时,参数会被压入栈中。基址指针同样能作为访问这些参数的参考。参数通常位于基址指针上方(偏移量为正)的位置,程序可以依据偏移量来访问传入的参数。比如,第一个参数可能位于基址指针偏移量为 +8 字节的位置,程序就能通过 ebp + 8 来获取该参数的值。

4. 恢复调用函数的栈帧
当函数执行完毕需要返回时,需要恢复调用函数的栈帧。基址指针在此时发挥重要作用,因为它保存着调用函数栈帧的基地址。通过恢复基址指针的值,程序可以准确回到调用函数的栈帧,继续执行后续代码。

示例说明
以下是一段简单的 C 语言代码示例:

#include <stdio.h>

int add(int a, int b) {
    int result = a + b;
    return result;
}

int main() {
    int x = 3;
    int y = 5;
    int sum = add(x, y);
    printf("The sum is: %d\n", sum);
    return 0;
}

在这个示例中,当 main 函数调用 add 函数时:

  • add 函数会保存 main 函数的基址指针(ebp)到栈中。
  • add 函数设置新的基址指针,指向 add 函数栈帧的底部。
  • add 函数通过相对于新基址指针的偏移量来访问参数 ab 以及局部变量 result
  • add 函数执行完毕返回时,会恢复 main 函数的基址指针,从而回到 main 函数的栈帧继续执行。

综上所述,基址指针在函数调用和栈帧管理过程中是不可或缺的,它为程序准确访问栈上的数据和恢复调用上下文提供了重要支持。

5.2 栈帧
可以这么理解,基址指针(在 x86 架构里通常是 ebp 寄存器,64 位系统中是 rbp)指向的是当前函数栈帧的起始位置(底部),下面详细解释:

栈帧的概念
在程序运行时,每当调用一个函数,系统会在栈上为该函数分配一块连续的内存区域,这个区域就叫做栈帧。每个栈帧包含了函数执行所需的各种信息,如函数的参数、局部变量、返回地址以及旧的基址指针等。

基址指针与栈帧起始位置

  • 指向当前栈帧底部:基址指针在函数调用时被设置为指向当前函数栈帧的起始位置(底部)。在函数执行过程中,它就像一个固定的参考点,程序可以通过相对于基址指针的偏移量来准确访问栈帧内的各种数据。
  • 辅助数据访问:借助基址指针,函数能够方便地找到自己的参数和局部变量。例如,参数通常存放在基址指针上方(地址值更大)的栈内存区域,通过基址指针加上特定的偏移量就能访问;而局部变量一般在基址指针下方(地址值更小),通过基址指针减去相应偏移量来访问。

示例说明
结合之前的 add 函数和 main 函数示例:

#include <stdio.h>

int add(int a, int b) {
    int result = a + b;
    return result;
}

int main() {
    int x = 3;
    int y = 5;
    int sum = add(x, y);
    printf("The sum is: %d\n", sum);
    return 0;
}
  • main 函数:在 main 函数执行时,基址指针指向 main 函数栈帧的底部。这个栈帧包含了 main 函数的局部变量 xysum 等。
  • 调用 add 函数:当 main 函数调用 add 函数时,会进行以下操作:
    • main 函数的基址指针(ebp)的值压入栈中保存。
    • 将当前栈指针(esp)的值赋给基址指针寄存器(ebp),此时 ebp 就指向了 add 函数栈帧的起始位置(底部)。
    • add 函数的局部变量(如 result)在栈上分配空间。
  • add 函数执行:在 add 函数执行期间,通过相对于 ebp 的偏移量来访问参数 ab 和局部变量 result
  • add 函数返回add 函数执行完毕后,会从栈中弹出之前保存的 main 函数的基址指针值,恢复到 ebp 寄存器,这样 ebp 又重新指向 main 函数栈帧的底部。

所以,基址指针在不同函数调用时,会指向当前正在执行的函数栈帧的起始位置,以此来辅助函数访问栈上的数据和管理栈帧。


http://www.kler.cn/a/600638.html

相关文章:

  • 《大语言模型》学习笔记(四)--Transformer 模型
  • 大模型思维链COT:Chain-of-Thought Prompting Elicits Reasoningin Large Language Models
  • k8s存储介绍(二)Secret
  • 爬虫豆瓣电影
  • 国内常用各类证件照的尺寸,证件照尺寸大小汇总【免费改图网站】
  • 《Python实战进阶》第33集:PyTorch 入门-动态计算图的优势
  • 微软纳德拉最新一期访谈
  • 基于Java,SpringBoot和Vue高考志愿填报辅助系统设计
  • aab 转 apk
  • 前端安全加密方式
  • mknod命令与device_create函数的关系
  • 类和对象—封装
  • KNN算法+鸢尾花分类+手写数字识别案例
  • Swift实现嵌套json字典重排序并输出string
  • 树状数组模板
  • leetcode 之(移除元素)
  • 分布式架构-Spring技术如何能实现分布式事务
  • Matlab教程002:Matlab变量和基本运算符的使用
  • 【Redis】深入解析 Redis 五大数据结构
  • 【构建性能分析插件设计与实现:打造前端项目的性能透视镜】