C语言----函数栈帧讲解

目录

1.函数栈帧是什么?

2. 理解函数栈帧能解决什么问题 

3、函数栈帧的创建和销毁具体过程

3.1 什么是栈

3.2 认识相关寄存器和汇编指令

3.3函数栈帧的创建和销毁

3.3.1 预备知识

3.3.2 函数的调用堆栈

3.3.3 准备环境

3.3.4 转到反汇编

3.3.5 函数栈帧的创建

3.3.6 函数栈帧的销毁

相关概念知识(辅助理解):

1.栈(Stack)

2. esp 和 ebp 的作用

3. 寄存器

1.通用寄存器(General-Purpose Registers)

2. 段寄存器(Segment Registers)

3. 控制寄存器(Control Registers)

4. 关键寄存器详解:


1.函数栈帧是什么?

在C语言中书写代码时, 我们通常会把一个独立的功能用函数来实现, 不同的函数用来实现不同的功能,  所以C程序是以函数为基本单位的。  那函数是如何被调用的?  函数的返回值又是如何待会的?  ​​​​​函数的形参和实参是如何传递的?  这些问题都和函数栈帧有关系。

函数栈帧(Stack Frame) 是函数运行时在内存栈(Stack)中占用的一个独立空间,用来存储该函数运行所需的所有临时数据。

独立空间所存放的数据包括:

函数参数和函数返回的地址(函数返回值)

旧的基指针(保存调用者(Caller)的栈帧基址(EBP/RBP))

局部变量和临时数据

2. 理解函数栈帧能解决什么问题 

只要理解了函数栈帧的创建和销毁,就能大概弄懂一下的问题

  • 局部变量是如何创建的?
  • 为什么局部变量不初始化内容是随机的?
  • 函数调用时参数时如何传递的?传参的顺序是怎样的?
  • 函数的形参和实参分别是怎样实例化的?
  • 函数的返回值是如何带会的?

3、函数栈帧的创建和销毁具体过程

3.1 什么是栈

栈(Stack)是现代计算机程序的核心基础之一,几乎所有程序都依赖它运行。

简单来说,栈就像一个严格遵守"后来先出"规则的容器:数据像叠盘子一样被压入(push)栈顶,取出时也只能从最上面弹出(pop)。

在计算机中,栈是一块特殊的内存区域,由CPU通过栈指针寄存器(如x86架构的ESP/RSP)自动管理,随着数据压入栈顶指针向低地址移动(栈向下增长),弹出时则向高地址回退。

正是这个精巧的设计,使得函数调用、局部变量存储、参数传递等关键功能得以实现,可以说没有栈就没有现代编程语言中的函数概念。

3.2 认识相关寄存器和汇编指令

相关寄存器:

1.eax:通用寄存器,保留临时数据,常用于返回值

2.ebx:通用寄存器,保留临时数据

3.ebp:栈底寄存器

4.esp:栈顶寄存器

5.eip:指令寄存器,保存当前指令的下一条指令的地址

汇编指令:

1.call:保存下一条指令地址(返回地址)到栈顶,并跳转到目标函数

2.ret:从栈顶弹出返回地址,跳转回调用位置继续执行。     

3.push:将数据压入栈顶,栈指针下移(栈向低地址增长)。 

4.pop:从栈顶弹出数据,栈指针上移。

5.enter:建立新栈帧(保存旧帧指针,分配局部变量空间)。 

6.leave:撤销当前栈帧(恢复旧帧指针和栈指针)。                               

7.mov (ebp/esp):直接操作帧指针(ebp)或栈指针(esp),用于调整栈结构。

8.sub/add (esp):动态调整栈空间(如分配/释放局部变量)。

3.3函数栈帧的创建和销毁

3.3.1 预备知识

首先我们达成一些预备知识才能有效的帮助我们理解,函数栈帧的创建和销毁。

1.每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。

2.这块空间的维护是使用了2个寄存器: esp 和 ebp , ebp 记录的是栈底的地址, esp 记录的是栈顶的地址。                                     

3. 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异。

如图:

3.3.2 函数的调用堆栈

演示代码:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int Mystrlen(char* arr)
{if (*arr != '\0')return	1+Mystrlen(arr+1);elsereturn 0;
}
int main()
{char arr1[10] = "abcdedg";int len = Mystrlen(arr1);printf("%d", len);return 0;
}

这段代码,如果我们在VS2022编译器上调试,调试进入Mystrlen函数后,我们就可以观察到函数的调用堆栈(右击勾选【显示外部代码】)如下图:

打开方法:

通过菜单栏打开: 启动调试(按 F5 或点击 调试 >)

开始调试在调试状态下,点击菜单栏的 调试 (Debug)选择 窗口 (Windows) > 调用堆栈 (Call Stack)。

快捷键:Ctrl + Alt + C(默认)。

函数调用堆栈是反馈函数调用逻辑的,那我们可以清晰的观察到, main 函数调用之前,是由 invoke_main 函数来调用main函数。在 invoke_main 函数之前的函数调用我们就暂时不考虑了。那我们可以确定, invoke_main 函数应该会有自己的栈帧, main 函数和 Add 函数也会维护自己的栈帧,每个函数栈帧都有自己的 ebp 和 esp 来维护栈帧空间。那接下来我们从main函数的栈帧创建开始讲解:

3.3.3 准备环境

为了让我们研究函数栈帧的过程足够清晰,不要太多干扰,我们可以关闭下面的选项,让汇编代码中排除一些编译器附加的代码:

3.3.4 转到反汇编

调试到main函数开始执行的第一行,右击鼠标转到反汇编。
注:VS编译器每次调试都会为程序重新分配内存,课件中的反汇编代码是一次调试代码过程中数据,每次调试略有差异。

int main()
{
//函数栈帧的创建
00007FF79DB71900  push        rbp  
00007FF79DB71902  push        rdi  
00007FF79DB71903  sub         rsp,148h  
00007FF79DB7190A  lea         rbp,[rsp+20h]  
00007FF79DB7190F  lea         rdi,[rsp+20h]  
00007FF79DB71914  mov         ecx,2Ah  
00007FF79DB71919  mov         eax,0CCCCCCCCh  
00007FF79DB7191E  rep stos    dword ptr [rdi]  
00007FF79DB71920  mov         rax,qword ptr [__security_cookie (07FF79DB7D000h)]  
00007FF79DB71927  xor         rax,rbp  
00007FF79DB7192A  mov         qword ptr [rbp+118h],rax  
00007FF79DB71931  lea         rcx,[__167BB7BA_源@c (07FF79DB82008h)]  
00007FF79DB71938  call        __CheckForDebuggerJustMyCode (07FF79DB71370h)  
00007FF79DB7193D  nop  
//main函数中的核心代码char arr1[10] = "abcdedg";
00007FF79DB7193E  mov         rax,qword ptr [string "abcdedg" (07FF79DB7AC70h)]  
00007FF79DB71945  mov         qword ptr [arr1],rax  
00007FF79DB71949  lea         rax,[rbp+60h]  
00007FF79DB7194D  mov         rdi,rax  
00007FF79DB71950  xor         eax,eax  
00007FF79DB71952  mov         ecx,2  
00007FF79DB71957  rep stos    byte ptr [rdi]  int len = Mystrlen(arr1);
00007FF79DB71959  lea         rcx,[arr1]  
00007FF79DB7195D  call        Mystrlen (07FF79DB713DEh)  
00007FF79DB71962  mov         dword ptr [len],eax  printf("%d", len);
00007FF79DB71968  mov         edx,dword ptr [len]  
00007FF79DB7196E  lea         rcx,[string "%d" (07FF79DB7ACB4h)]  
00007FF79DB71975  call        printf (07FF79DB7119Ah)  
00007FF79DB7197A  nop  return 0;
00007FF79DB7197B  xor         eax,eax  
}
3.3.5 函数栈帧的创建

这里我看到 main 函数转化来的汇编代码如上所示。接下来我们就一行行拆解汇编代码:

00007FF79DB71900  push        rbp  
//将调用者(如invoke_main)的栈基址rbp压栈保存,esp自动-8(x64下指针占8字节)
//此时rsp指向栈顶,保存了旧的rbp值00007FF79DB71902  push        rdi  
//保存rdi寄存器的值到栈中(x64调用约定中rdi可能被调用者修改),esp再-800007FF79DB71903  sub         rsp,148h  
//给main函数分配栈空间:rsp减去0x148字节(328字节)
//现在rsp指向main函数栈帧的顶部,与后续的rbp构成栈帧范围00007FF79DB7190A  lea         rbp,[rsp+20h]  
//设置main函数的栈基址rbp = rsp + 0x20
//这样rbp到rsp之间保留0x20字节(可能用于调试或局部变量)00007FF79DB7190F  lea         rdi,[rsp+20h]  
//将rdi指向栈初始化区域的起始地址(rbp的位置),准备填充0xCC00007FF79DB71914  mov         ecx,2Ah  
//设置循环次数ecx = 0x2A(42次),每次处理4字节,共初始化42*4=168字节00007FF79DB71919  mov         eax,0CCCCCCCCh  
//用调试模式填充值0xCCCCCCCC初始化栈空间(未初始化内存的标记)00007FF79DB7191E  rep stos    dword ptr [rdi]  
//从rdi指向的地址开始,重复填充eax的值(0xCCCCCCCC)到内存,共ecx次
//相当于初始化[rbp-0x20]到[rbp+0xA8]的范围(168字节)00007FF79DB71920  mov         rax,qword ptr [__security_cookie (07FF79DB7D000h)]  
// 从全局变量加载安全cookie(栈溢出保护值)到rax00007FF79DB71927  xor         rax,rbp  
//将安全cookie与当前栈基址rbp异或,生成唯一校验值00007FF79DB7192A  mov         qword ptr [rbp+118h],rax  
//将校验值存入栈中[rbp+0x118]的位置(函数返回时会验证是否被篡改)00007FF79DB71931  lea         rcx,[__167BB7BA_源@c (07FF79DB82008h)]  
//加载调试信息符号地址到rcx(用于"Just My Code"调试功能)00007FF79DB71938  call        __CheckForDebuggerJustMyCode (07FF79DB71370h)  
//调用VS调试器检查函数,确认是否在调试模式下运行00007FF79DB7193D  nop  
//空指令(用于对齐或预留调试断点位置)

上面的这段代码,等价于下面的伪代码:

void main() {// 1. 保存调用者的栈基址和寄存器push(rbp);          // 保存invoke_main的rbppush(rdi);          // 保存可能被修改的rdi// 2. 分配栈空间(x64下更大)rsp -= 0x148;       // 分配328字节空间// 3. 设置新的栈基址(跳过预留区域)rbp = rsp + 0x20;   // rbp指向有效栈帧起始处// 4. 初始化栈空间(填充0xCC)rdi = rbp;          // 初始化起始地址ecx = 42;           // 循环次数(42次×4字节=168字节)eax = 0xCCCCCCCC;memset(rdi, eax, ecx * 4); // 填充168字节// 5. 栈溢出保护(x64特有)rax = __security_cookie;      // 加载安全cookierax ^= rbp;                   // 与栈基址异或加密*(rbp + 0x118) = rax;         // 存储校验值// 6. 调试检查(VS特有)if (IsDebuggerPresent()) {    // 检查调试器__CheckForDebuggerJustMyCode(); // 调试钩子}
}

小知识 : 烫烫烫烫烫烫烫烫烫烫烫烫

出现 “烫烫烫……” 的原因是:在 Windows 下,未初始化的栈内存可能会被初始化为 0xCC ,而 0xCC 对应的字符在当前字符编码下显示为 “烫” 。

接下来我们再分析main函数中的核心代码:

1. 初始化字符数组 arr1[10] = "abcdedg";

00007FF79DB7193E  mov         rax, qword ptr [string "abcdedg" (07FF79DB7AC70h)]  
//从全局数据段(地址 `07FF79DB7AC70h`)加载字符串 `"abcdedg"` 的前 8 字节到 `rax`。
//由于 `"abcdedg"` 是 7 字节(含 `\0`),rax 会包含'a','b','c','d','e','d','g','\0'00007FF79DB71945  mov         qword ptr [arr1], rax  
//将 `rax` 的值(即字符串的前 8 字节)存储到 `arr1` 的起始地址(`[arr1]`)。
//此时 `arr1` 的前 8 字节已填充为 `"abcdedg\0"`。00007FF79DB71949  lea         rax, [rbp+60h]  
//计算 `arr1` 的剩余部分地址(`rbp+60h`)
//即 `arr1[8]` 的位置(因为 `arr1` 是 `char[10]`,前 8 字节已填充,剩余 2 字节)。00007FF79DB7194D  mov         rdi, rax  
//将目标地址 `rbp+60h` 存入 `rdi`(`stos` 指令的目的寄存器)。00007FF79DB71950  xor         eax, eax  
//清零 `eax`,即 `al = 0`(`\0` 字符)。00007FF79DB71952  mov         ecx, 2  
//设置循环次数 `ecx = 2`(剩余 2 字节需要填充 `\0`)。00007FF79DB71957  rep stos    byte ptr [rdi]  
//从 `rdi` 指向的地址开始,重复填充 `al`(`0`)到内存,共 `ecx` 次(2 次)。
//相当于 `arr1[8] = '\0'; arr1[9] = '\0';`,确保数组完全以 `\0` 结尾。

2. 调用 Mystrlen(arr1) 计算字符串长度

00007FF79DB71959  lea         rcx, [arr1]  
//将 `arr1` 的地址加载到 `rcx`(x64 调用约定:第一个参数用 `rcx` 传递)。00007FF79DB7195D  call        Mystrlen (07FF79DB713DEh)  
//调用 `Mystrlen` 函数,返回值存储在 `eax` 中。00007FF79DB71962  mov         dword ptr [len], eax  
//将返回值(字符串长度)存入局部变量 `len`。

3. 调用 printf 打印长度

00007FF79DB71968  mov         edx, dword ptr [len]  
//将 `len` 的值(`7`)存入 `edx`(x64 调用约定:第二个参数用 `edx` 传递)。00007FF79DB7196E  lea         rcx, [string "%d" (07FF79DB7ACB4h)]  
//加载格式字符串 `"%d"` 的地址到 `rcx`(第一个参数)。00007FF79DB71975  call        printf (07FF79DB7119Ah)  
//调用 `printf`,输出 `7`。00007FF79DB7197A  nop  
//空指令(对齐或占位)。

4. 返回 0

00007FF79DB7197B  xor         eax, eax  
//将 `eax` 清零(`return 0;` 的常见优化写法)。
3.3.6 函数栈帧的销毁

当函数调用要结束返回的时候,前面创建的函数栈帧也开始销毁。那具体是怎么销毁的呢?我们看一下反汇编代码。

00007FF773182288  lea     rsp, [rbp+0C8h]  
//将 rsp 直接设置为 rbp + 0C8h,相当于回收整个函数的栈空间(esp = ebp + 分配的大小)  
//此时 rsp 指向调用者栈帧的栈顶(函数调用前的 rsp 值)  00007FF77318228F  pop     rdi  
//从栈顶弹出一个值,存放到 rdi 中(恢复调用者的 rdi 寄存器),rsp + 8(x64 下指针占 8 字节)  00007FF773182290  pop     rbp  
//从栈顶弹出一个值,存放到 rbp 中,此时栈顶的值就是调用者的 rbp(恢复调用者的栈基址),rsp + 8  00007FF773182291  ret  
//ret 指令的执行:  
//1. 从栈顶弹出一个值(此时栈顶的值就是 call 指令下一条指令的地址),rsp + 8  
//2. 跳转到该地址,继续执行调用者的代码  

这样之后就会跳转到main函数内继续执行代码

本章结束 以上就是函数栈帧创建和销毁

以下是一些概念知识 需要的可自行阅读


相关概念知识(辅助理解):

1.栈(Stack)

栈是一种后进先出(LIFO)的数据结构,在内存中从高地址向低地址增长。在函数调用时,栈用于:

  • 存储函数参数(由调用者压栈)
  • 保存返回地址(call指令自动压入)
  • 保存调用者的ebp(被调函数保存)
  • 分配局部变量
  • 存储临时数据(如运算中间结果)

2. esp 和 ebp 的作用

寄存器全称作用
espExtended Stack Pointer始终指向栈的当前顶部(最低可用地址),随push/pop动态变化
ebpExtended Base Pointer指向当前函数栈帧的基地址,用于定位局部变量和参数

esp 的特点

  • 动态变化,每次pushpopsub esp, N(分配空间)或add esp, N(释放空间)都会改变。
  • 在函数调用时,esp会调整以容纳新的栈帧。

ebp 的特点

  • 在函数执行期间固定,作为局部变量和参数的基准。
  • 通过[ebp + offset]访问参数,[ebp - offset]访问局部变量。

3. 寄存器

寄存器(Registers)是CPU内部的高速存储单元,用于临时存放数据、地址和控制信息。在函数调用和栈帧管理中,关键的寄存器包括 通用寄存器段寄存器 和 控制寄存器

1.通用寄存器(General-Purpose Registers)

这些寄存器可用于计算、寻址和数据传输,主要分为:

寄存器名称主要用途
eaxAccumulator存放函数返回值、算术运算
ebxBase数据存储(较少用于计算)
ecxCounter循环计数(如rep指令)
edxData辅助eax(如乘法/除法的高位结果)
esiSource Index字符串/数组操作的源指针
ediDestination Index字符串/数组操作的目标指针
espStack Pointer指向栈顶(动态变化)
ebpBase Pointer指向当前栈帧基址(固定)
2. 段寄存器(Segment Registers)

用于内存分段(现代操作系统已较少使用):

寄存器名称用途
csCode Segment代码段基址
dsData Segment数据段基址
ssStack Segment栈段基址(esp/ebp默认在此段)
esfsgsExtra Segments附加数据段
3. 控制寄存器(Control Registers)
寄存器名称用途
eipInstruction Pointer指向下一条要执行的指令(不可直接修改)
eflagsFlags存储状态标志(如零标志ZF、进位标志CF
4. 关键寄存器详解:

(1)esp(Stack Pointer)

  • 作用:始终指向栈的当前顶部(即最后入栈的数据地址)。
  • 变化规则
    • push 时:esp 减小(栈向低地址增长)。
    • pop 时:esp 增大
    • 函数调用时,esp 会动态调整以分配/释放栈空间。

(2)ebp(Base Pointer)

  • 作用:指向当前函数栈帧的基地址,用于:
    • 定位局部变量([ebp - offset])。
    • 访问函数参数([ebp + offset])。
  • 特点
    • 在函数执行期间固定不变(除非手动修改)。
    • 通过 mov ebp, esp 在函数开头建立栈帧。

本博客借鉴于:函数栈帧的创建与销毁(超详解)-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/902968.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录学习笔记---二叉树

学习目标&#xff1a; 学习代码随想录–二叉树 每天学习1道,复习两道 学习内容&#xff1a; 2025.4.7 复习内容: 24. 两两交换链表中的节点 25. 最大二叉树 学习内容 26. 合并二叉树 2025.4.8 复习内容: 27. 二分查找 28. 合并二叉树 29. 27. 移除元素 学习内容: 30. 二叉…

Git ——提交至github,Vercel拉取,更新不了项目的问题解决

首先因为github上有个错误 1 failing check Vercel - No GitHub account was found matching the commit author email address 发现好像是vercel拉取不了项目&#xff0c;vercel登录的邮箱与我此次提交更改的邮箱不匹配&#xff0c;查看Git的user确实如此&#xff08;之前的…

Vue3项目中 npm 依赖安装 --save 与 --save-dev 的区别解析

这两个命令的区别如下&#xff1a; bash npm install --save types/crypto-js # 安装到 dependencies&#xff08;生产依赖&#xff09; npm install --save-dev types/crypto-js # 安装到 devDependencies&#xff08;开发依赖&#xff09; 核心区别 依赖分类不同…

品牌如何通过朝日新闻出海日本?——某企业日本媒体发稿实战

文 | 言同数字亚太传播实验室 一、日本市场的隐形门槛&#xff1a;中国品牌的三大痛点 案例背景&#xff1a; 某中国灵芝保健品企业&#xff08;代号"ForestLife"&#xff09;&#xff0c;产品虽获中国/欧盟有机认证&#xff0c;但在日本市场面临&#xff1a; 认知…

鸿蒙-试一下属性字符串:除了Span之外,如何在同一个Text组件中展示不同样式的文字

文章目录 前言简介有哪些类型拉出来溜溜Text SpanStyledString其他CustomSpan先看一下构造函数onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetricsonDraw(context: DrawContext, drawInfo: CustomSpanDrawInfo) 遗留问题 前言 在开发中&#xff0c;经常会遇到…

Nginx 安装与配置全流程指南(2025 最新版)

一、环境准备与依赖安装 1.1 系统要求 操作系统&#xff1a;支持主流 Linux 发行版&#xff08;Ubuntu 20.04/CentOS 7/Debian 10&#xff09;硬件配置&#xff1a;内存 ≥512MB&#xff0c;磁盘 ≥10GB 可用空间&#xff08;建议使用 SSD&#xff09;网络要求&#xff1a;开…

【LeetCode 热题 100】滑动窗口最大值 / 最小覆盖子串 / 轮转数组 / 缺失的第一个正数

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;LeetCode 热题 100 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 子串和为 K 的子数组滑动窗口最大值最小覆盖子串 普通数组最大子数组和合并区间轮转数组除自身以外数组的乘积缺失的…

golang的cgo的一点小心得

最后有个项目需要涉及到cgo&#xff0c;在这块以前用的不多&#xff0c; 这次略微用得深入了一点&#xff0c;记下来几点以备以后使用 本质上cgo去用的时候就是遵守一些ABI而已&#xff0c;总体而言&#xff0c;尽量避免复杂结构的来回传递。1 对于变长参数&#xff0c;只有…

异构网络环境下的切换策略研究

移动互联网应用快速崛起,现有的无线接入技术有,无线局域网(Wireless Local Area NetWork,WLAN),移动蜂窝网络(4G,5G),无线广域网(Wireless Wide Area Network,WWAL)以及卫星通信网络等。多接入技术方便用户通信,还符合多业务场景。这种多无线接入技术共存的网络环…

人工智能赋能美妆零售数字化转型:基于开源AI大模型的S2B2C商城系统构建

摘要 在消费升级背景下&#xff0c;美妆行业正经历从传统卖场向智能体验空间的转型。本文以"未来商店"为研究对象&#xff0c;探讨开源AI大模型与S2B2C商城系统的协同效应&#xff0c;揭示人工智能技术如何重构"人-货-场"关系。通过实证研究发现&#xff…

计算机视觉中的正则化:从理论到实践的全面解析

&#x1f31f; 计算机视觉中的正则化&#xff1a;从理论到实践的全面解析&#x1f31f; 大家好&#xff01;今天要和大家分享的是在计算机视觉&#xff08;CV&#xff09;领域中非常重要的一个概念——正则化&#xff08;Regularization&#xff09;。无论你是刚开始接触深度学…

Linux字符设备驱动开发的详细步骤

1. 确定主设备号​​ ​​手动指定​​&#xff1a;明确设备号时&#xff0c;使用register_chrdev_region()静态申请&#xff08;需确保未被占用&#xff09;。​​动态分配​​&#xff1a;通过alloc_chrdev_region()由内核自动分配主设备号&#xff08;更灵活&#xff0c;推…

软件工程效率优化:一个分层解耦与熵减驱动的系统框架

软件工程效率优化&#xff1a;一个分层解耦与熵减驱动的系统框架** 摘要 (Abstract) 本报告构建了一个全面、深入、分层的软件工程效率优化框架&#xff0c;旨在超越简单的技术罗列&#xff0c;从根本的价值驱动和熵减原理出发&#xff0c;系统性地探讨提升效率的策略与实践。…

【Docker游戏】使用Docker部署vue-XiuXianGame文字修仙小游戏

【Docker游戏】使用Docker部署vue-XiuXianGame文字修仙小游戏 一、vue-XiuXianGame介绍1.1 vue-XiuXianGame简介1.2 主要特点 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、拉…

用 LangChain 手搓 RAG 系统:从原理到实战

一、RAG 系统简介 在当今信息爆炸的时代&#xff0c;如何高效地从海量数据中获取有价值的信息并生成准确、自然的回答&#xff0c;成为了人工智能领域的重要课题。检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;RAG&#xff09;系统应运而生&#xff0c;…

SpringBoot集成LiteFlow实现轻量级工作流引擎

LiteFlow 是一款专注于逻辑驱动流程编排的轻量级框架&#xff0c;它以组件化方式快速构建和执行业务流程&#xff0c;有效解耦复杂业务逻辑。通过支持热加载规则配置&#xff0c;开发者能够即时调整流程步骤&#xff0c;将复杂的业务如价格计算、下单流程等拆分为独立且可复用的…

38 python random

在实际中,我们常常会用到随机的概念,比如 模拟抽奖活动(如:月度优秀员工抽奖)生成测试数据(如:随机考勤时间、随机销售额)打乱数据顺序(如:随机分配任务到人)Python 的random模块就像你的 "随机事件生成器",帮你轻松创建各种随机数据 一、基础操作:从随…

附赠二张图,阐述我对大模型的生态发展、技术架构认识。

文章精炼&#xff0c;用两张图说明大模型发展业态方向&#xff0c;以及大模型主体技术架构。&#xff08;目前还需要进一步验证我的Thought && ideas&#xff0c;等待机会吧.........&#xff09; 图一&#xff1a;探究大模型三个层次应用方向&#xff0c;浅层次入门简…

2025上海车展 | 移远通信全栈车载智能解决方案重磅亮相,重构“全域智能”出行新范式

2025年4月23日至5月2日&#xff0c;第二十一届上海国际汽车工业展览会在国家会展中心&#xff08;上海&#xff09;盛大启幕。作为车载智能解决方案领域的领军企业&#xff0c;移远通信以“全域智能 驭见未来”为主题&#xff0c;携丰富的车载解决方案及客户终端惊艳亮相8.2馆8…

告别 “幻觉” 回答:RAG 中知识库与生成模型的 7 种对齐策略

一、引言 大语言模型&#xff08;LLM&#xff09;在文本生成领域展现出惊人能力&#xff0c;但 “幻觉” 问题&#xff08;生成虚构或偏离事实的内容&#xff09;始终是落地应用的核心挑战。检索增强生成&#xff08;RAG&#xff09;通过将外部知识库与 LLM 结合&#xff0c;形…