函数栈帧的创建与销毁(反汇编万字讲解)

news2024/12/23 7:26:36

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C语言学习分享⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多C语言知识
  🔝🔝


在这里插入图片描述


这里写目录标题

  • 1. 前言🚩
  • 2. 前期铺垫🚩
    • 2.1 main函数的调用🏁
    • 2.2 调用main函数的函数🏁
  • 3. 利用反汇编深入了解栈帧建立🚩
    • 3.1 反汇编过程(创建栈帧)🏁
      • 3.11 创建栈帧第一步🏳️‍🌈
      • 3.12 创建栈帧第二步🏳️‍🌈
      • 3.13 创建栈帧第三步🏳️‍🌈
      • 3.14 创建栈帧第四步🏳️‍🌈
      • 3.15 创建栈帧第五步🏳️‍🌈
      • 3.16 创建栈帧第六步(随机值的产生)🏳️‍🌈
    • 3.2 反汇编(执行代码过程)🏁
      • 3.21 执行代码第一步(随机值的展现)🏳️‍🌈
      • 3.22 执行代码第二步(调用Add函数)🏳️‍🌈
      • 3.23 Add函数内🏳️‍🌈
      • 3.24 Call调用函数🏳️‍🌈
    • 3.3 Add函数内部🏁
      • 3.31 Add函数汇编第一步🏳️‍🌈
      • 3.32 Add函数汇编第二步🏳️‍🌈
  • 4. 利用反汇编深入了解函数栈帧的销毁🚩
    • 4.1 保存返回值🏁
    • 4.2 销毁栈帧第二步🏁
    • 4.3 销毁栈帧第三步🏁
    • 4.4 返回main函数🏁
  • 5. 总结🚩


1. 前言🚩


在我们前期学习C语言时,可能会有很多疑问 ? 比如:

  • 局部变量是怎么创建的?
  • 为什么未初始化的局部变量的值是随机值?
  • 函数是怎样传参的?传参的顺序是怎样的?
  • 形参和实参是什么关系?
  • 函数调用是怎样做的?
  • 函数调用后是怎样返回的?

我们本章就来研讨这个问题,掌握了函数栈帧的创建和销毁更有利于后期的学习这里建议大家要从头往后一个内容一个内容看,因为这里每一个部分关联性很强!

进入正题,我们使用的编译环境是vs2013.不要使用太高级别的编译器,越高级的编译器,越不容易学习和观察.同时在不同编译器下,函数调用过程中栈帧的创建是略微有点差别的,具体实现取决于编译器的实现


2. 前期铺垫🚩

我们之前应该听说过寄存器,寄存器中有eax,ebx,ecx,edx等寄存器,今天都能看见它们的身影,而今天的重点是这两个寄存器:ebp 和 esp,要理解函数栈帧就必须理解这两个寄存器.

ebp 和 esp 这两个寄存器是用来维护函数栈帧的.我们之前说过,内存存储一般分为几个区域,而每一次函数调用都是在栈区上创建一块儿空间,所以这里我们只讨论栈区.

在这里插入图片描述

我们这里先创建一个项目,简单的调用一个加法函数:

#include <stdio.h>

int Add(int x, int y)
{
    int z=0;
    z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);
	printf("%d ",c);
	return 0;
}

2.1 main函数的调用🏁

我们首先调用main函数就会为main函数在栈区开辟一块空间,这片空间被称为main函数的函数栈帧:假设绿色方格为栈区.(栈区习惯先使用高地址后使用低地址)

在这里插入图片描述


这个时候我们刚刚介绍的寄存器 ebp 和 esp就要上场"表演"了,ebp是指向一个地址的寄存器,它将指向当前调用函数的高地址位置,而 esp也是指向地址的寄存器,它将指向当前调用的函数的地址值位置.这两个寄存器来维护现在正在调用的函数:

在这里插入图片描述
当前我们调用的是main所以ebp和esp用来维护为main函数开辟的栈帧.而当我们调用Add函数后,ebp和esp就将指向main函数栈帧的区域变为指向Add函数栈帧的区域用来维护Add函数

这是首先我们需要了解的点,我们还通常将esp称为栈顶指针,将ebp称为栈低指针


2.2 调用main函数的函数🏁

其实在我们编译代码时,还有其他函数去调用了我们的main函数,这个具体为什么调用main函数这里我们不做深入探究,
这里我们只需要知道main函数也是别别人调用的这个调用函数的函数叫做: __tmainCRTStartup( )
这里还有个套娃结构,就是__tmainCRTStartup( )函数还被一个叫mainCRTStartup( )的函数调用


听到这里你可能有一点云里雾里摸不着头脑,但是多的我们不去探究,这里我们只需要知道一个叫 mainCRTStartup( ) 的函数调用了一个叫 __tmainCRTStartup( ) 的函数,并且 __tmainCRTStartup( ) 函数调用了我们的main函数就够了!


在我们的调试界面的调用堆栈可以看见这两个函数:

在这里插入图片描述


相当于在我们main函数的栈帧空间之前,还应该有两个栈帧空间是为 tmainCRTStartup( ) 函数
和 __tmainCRTStartup( ) 函数开辟的:

在这里插入图片描述
正在调用哪个函数,ebp和esp就指向哪块栈帧!



3. 利用反汇编深入了解栈帧建立🚩

首先,我们写好简单的代码后按F11,然后什么都别点,点击右键转到反汇编,这里就可以看见我们的汇编代码了!(注意:视频用的是VS2022,所以可能会和我们之后的图片不符合,这里按照vs2013编译器来):

转到反汇编


这里我们在vs2013上可以看见汇编代码:我们一行一行往下走
在这里插入图片描述


3.1 反汇编过程(创建栈帧)🏁

首先我们可以看见它上来正在准备调用main函数,我们知道前面有一个函数是用来调用main函数的,所以我们先把调用main函数的函数的图给画出来:

在这里插入图片描述


当我们画好图后就开始一步一步走汇编代码: 紧跟着我的节奏!

3.11 创建栈帧第一步🏳️‍🌈

在这里插入图片描述

第一步的操作是:push,push的对象是ebp,push是压栈的意思,相当于在__tmainCRTStartup函数的栈帧上面插入了一个元素.并且esp跟着向上运动一格(地址减一,因为栈区是从高地址向低地址走

在这里插入图片描述


3.12 创建栈帧第二步🏳️‍🌈

在这里插入图片描述

第二步的操作是 mov,就是move的意思,就是将esp的值赋值给ebp,相当于esp和ebp现在指向同一块地址

在这里插入图片描述


3.13 创建栈帧第三步🏳️‍🌈

在这里插入图片描述

第三步的操作是sub,就是减法的意思,这里就是将esp减去一个0E4h,0E4h是一个8进制数字,它的值是228.不管怎么说,就是esp减去了一个值,相当于esp变小了,它就不指向原先的位置了,而是指向上面的某块位置

在这里插入图片描述

而当我们执行到这一步会发现,esp和ebp中间又重新多出来了一块空间,实际上这块空间就是为我们main函数开辟的栈帧


3.14 创建栈帧第四步🏳️‍🌈

在这里插入图片描述

当我们为main函数开辟好栈帧后,紧接着又执行了三次push,分别将ebx,esi,edi压栈,这三个元素这里我们暂且不做深入讨论,我们只需要知道它压入了三个元素.push一次,我们的esp就会变化一次,所以最终push完成三个元素后:

在这里插入图片描述


3.15 创建栈帧第五步🏳️‍🌈

在这里插入图片描述

第五步的操作是lea,它的意思是: load effective address,是加载有效地址的意思,它的意思就是将后面的 [ebp-0E4h] 加载到edi里面去.现在我们还看不出上面的效果,暂时先放着,我们接着往后走:


3.16 创建栈帧第六步(随机值的产生)🏳️‍🌈

在这里插入图片描述

这三步比较抽象,具体意思就是 dword 就是四个字节的意思,就是每次操作四个字节,将我们从 edi 开始的位置向下ecx个空间也就是 39h 这么多个空间全部改成 eax 的内容,也就是 CCCCCCCC. 我们再去内存中查看我们刚刚修改的空间时会看见它们全部被赋值成了C:

在这里插入图片描述

上面的这三步操作相当于把我们为main函数开辟的栈帧的内存全部初始化成了CCCC,这也就是我们未初始化的局部变量是随机值的原因.

在这里插入图片描述


3.2 反汇编(执行代码过程)🏁

在我们为main开辟好栈帧并且初始化之后,我们终于要来到执行代码的这一步了!

3.21 执行代码第一步(随机值的展现)🏳️‍🌈

在这里插入图片描述

第一步操作是mov,就是将0Ah赋值给我们的 [ebp-8] ,注意,这里的0Ah实际上就是我们初始化变量a的值,为10.而[ebp-8]就是ebp往上移动8格的位置.这里相当于我们的ebp-8的位置就是a变量的空间,空间里面放上10.

在这里插入图片描述

然而假设我们的a没有初始化给10,这里就不会把有效值放在内存中,内存里还是我们之前初始化的CCCCCC.这里就形成了随机值!


同理,这里定义的变量b和c也是以这种方式被存储在栈帧中.

在这里插入图片描述
最终得到:

在这里插入图片描述

注意:a,b,c三个变量存放位置的间隔不同编译器下可能不一样,这里不需要细究它为什么在什么位置,我们只需要了解局部变量是怎样创建的就好了


3.22 执行代码第二步(调用Add函数)🏳️‍🌈

这里就走到调用Add函数的地方了,函数调用需要传参,我们来看看是怎么回事

在这里插入图片描述


先看第一步,将[ebp-14h]的值赋值到eax上.我们会发现[ebp-14h]实际上就是存放b变量的值的地方,相当于就是将b的值20放到eax里面去

在这里插入图片描述


3.23 Add函数内🏳️‍🌈

在这里插入图片描述

这里首先将eax压栈,eax就放在了最上面,并且eax的值是20,然后esp再往上走一格得到:

在这里插入图片描述


紧接着往下走两步:

在这里插入图片描述

这里和前面非常类似,先将[ebp-8]的值赋值给ecx,再将ecx压栈最上面,再将esp往上走一格.并且这里的[ebp-8]其实就是我们变量a存放的值,为10.相当于就是将10赋值给ecx,再将ecx压栈.

在这里插入图片描述

我们上面的两个动作实际上是在传参,将参数a,b传到Add函数当中,并且,函数传参的顺序是先传参数b后传参数a!


3.24 Call调用函数🏳️‍🌈

在这里插入图片描述

这里我们要先把执行call这一代码的地址记下来:00C2144B.并且记下call下一行的地址:00C21450.当我们执行到call语句后.我们去查看内容:

在这里插入图片描述

它的栈帧中新压栈进入了一个东西:00 C2 14 50(这里要倒着读).这其实就是我们刚刚说要记住的地址,也就是call的下一行指令的地址 ,这里当我们执行完call指令后,我们就跳转到Add函数中去执行指令了,这里将call指令的下一条指令的地址给记住,把它放这儿,一会调用完Add函数后我们还要继续往下走,记住了下一条指令才能继续往下走,这里我们只需要知道这个被我们存储的地址等会儿要被使用到就可以了!

在这里插入图片描述


3.3 Add函数内部🏁

当我们执行完call指令后我们将跳转到Add函数的调用中.

在这里插入图片描述

不知道大家有没有发现,上面图片的汇编过程和我们之前探究main函数创建的汇编过程是一模一样的!这上面所有的内容其实就是在为我们的Add函数开辟栈帧,并且初始化为CCCC.这里和之前将的main函数开辟过程一样,所以我们直接往下走.

我们直接将执行完后这段汇编代码后的直观图带给大家:

在这里插入图片描述


3.31 Add函数汇编第一步🏳️‍🌈

在这里插入图片描述

我们初始化z为0,所以这里汇编代码将我们的0赋值给[ebp-8]这个位置,而ebp-8这个位置实际上就是变量z的空间

在这里插入图片描述


3.32 Add函数汇编第二步🏳️‍🌈

在这里插入图片描述

这里将[ebp+8]的值赋值给eax,而我们的ebp+8就是ebp这个指针往下走两格,我们看看其实就是我们刚刚存储的a的值10.

在这里插入图片描述


其实就是将我们形参a的值10赋值给我们的eax.紧接着看下面的操作

在这里插入图片描述

将a的值10赋值到eax后,下一步操作将[ebp+0Ch]的值加到eax上,而这里的[ebp+0Ch]刚好是我们保存的形参b的值20,这里将b的值加上去也就是得到了30.所以现在eax存放的值是30!紧接着我们将eax的值赋值给[ebp-8],而大家还记不记得,其实这里的[ebp-8]就是我们之前给C的一块空间,所以这里就是将eax的值30赋值给变量z,z现在也就变成了30.

在这里插入图片描述

讲到这儿,你可能就会对这句话有了一定的了解:形参是实参的一份临时拷贝.我们在调用函数之前,函数的参数就已经通过压栈的方式存储在了栈帧中,而当函数中需要用到参数时就会回去取,并且我们可以发现为函数形参开辟的空间并不在esp和ebp维护的函数中,也就是说函数的参数的空间和函数的空间是两个不一样的空间!



4. 利用反汇编深入了解函数栈帧的销毁🚩

在我们把Add函数中所有代码走了一遍后,现在要将z返回到main函数中去!

在这里插入图片描述

4.1 保存返回值🏁

在这里插入图片描述

这里return z的意思就是说将[ebp-8]也就是z的值30放在eax这个寄存器当中.我们说过当函数调用结束后里面创建的值会销毁,为了保存返回值z的值,这里就是将z的值赋值到eax寄存器中,寄存器是不会随着函数的销毁而销毁的,所以我们就成功的保存了z的值!


再往下走

在这里插入图片描述

pop就是出栈的意思,这里的三个pop就是将edi,esi,ebx分别出栈,出栈也就是从栈帧中删除的意思!每出栈一个元素,我们的esp就应该对应向下移动一格(和出栈刚好相反)

在这里插入图片描述



4.2 销毁栈帧第二步🏁

当Add函数调用完后,应该回收这块Add函数的栈帧,我们接着往下走:

在这里插入图片描述

这里先将ebp的值赋值给esp,相当于esp就不指向原先内个位置了,而是往下走了许多格和ebp指向一块栈帧.然后再将ebp给pop掉,也就是删除掉

在这里插入图片描述

而当我们pop掉这个ebp后,ebp就从维护Add函数的栈帧的位置重新跳到维护main函数的栈帧的位置了,因为这里Add函数以及调用完了,esp和ebp又要重新去维护正在调用的main函数了!

在这里插入图片描述

4.3 销毁栈帧第三步🏁

在这里插入图片描述

走到ret这个地方后,我们需要知道,此时我们已经将维护main函数的两个寄存器带到了正确的位置,但是我们执行完Add函数后要接着往下执行时,我们的return也就是ret要返回到什么位置呢?我们应该怎样找到这个位置?那大家还记不记得我们之前保存的call指令的下一个指令的地址,我们说call指令就是在调用函数,而调用完函数我们需要接着刚刚的call指令往下走,所以这里我们之前保存的call指令的下一条指令就起到作用了!我们的ret返回的位置也就是刚刚我们保存的00 C2 14 50.


在这里插入图片描述
这里箭头就指向call函数的下一个指令去了所以我们最初存call的下一个的指令的地址的意义就在这儿体现出来了


现在我们跳回去了接着看汇编指令:

在这里插入图片描述

这里将esp的值加上一个8,相当于esp向下移动了两个位置.这是因为我们调用完函数之后,形参x和y已经没有用了,所以我们将esp往下跳过两格就是刚好把形参x和y的栈帧给跳过去了,就把空间还给操作系统了

在这里插入图片描述


4.4 返回main函数🏁

在这里插入图片描述

将esp指向正确位置后,再将eax的值赋值给[ebp-20h],大家还记不记得,我们最开始将Add函数中z的值赋值给了eax这个寄存器,现在再将eax也就是z的值30赋值到[ebp-20h]这个位置.而这个位置刚好就是我们main函数里面的C.这一步相当于就是将Add的返回值赋值给C了

讲到这儿,Add函数的栈帧是怎样创建和销毁的,返回值是怎样被带出来了相信大家都有了一定的认识,后面的main函数的销毁和返回和Add函数大同小异,这里就不过多做介绍了.


5. 总结🚩

本篇文章主要是通过我们c语言的反汇编过程带大家深入了解了函数栈帧的创建和销毁,也许你在看文章的过程中看的云里雾里,摸不着头脑,没关系!本章作为C语言学习的番外篇着重带大家了解更底层的原理,你能理解,可能会对后面的学习又帮助,不能理解也没有关系!

其实我们可以看见C语言的编译过程是逻辑非常严谨的,不仅能走出去,还能回得来,什么时候创建栈帧,什么时候销毁栈帧它是给你安排的明明白白清清楚楚的,希望看了本篇文章你会更加喜欢C语言!


现在再去看看文章开头的问题,相信你也能回答出来了!


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

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

相关文章

AI为文档图像安全注入新力量

Hello大家好。我是Dream。 随着人工智能和大数据技术的快速发展&#xff0c;人们对于文档图像安全的关注度越来越高。尤其是在当下&#xff0c;AIGC取得了里程碑式的成绩&#xff0c;引发了市场广泛热烈的兴趣&#xff0c;扩散模型在内的关键技术取得突破&#xff0c;技术可用性…

链表的相关OJ题解析

目录 ⭐一、移除链表元素 ⭐二、反转链表 ⭐三、求链表中间节点 ⭐四、求链表倒数第k个节点 ⭐ 五、合并两个有序链表 ⭐六、链表的回文结构 ⭐ 七、相交链表 ⭐八、环形链表 ⭐九、链表入环的第一个节点 ⭐一、移除链表元素 链接&#xff1a; 移除链表元素 思路一…

浅尝css函数

文章目录 一、attr二、calc三、cubic-bezire四、conic-gradient五、counter六、hsl七、linear-gradient八、radial-gradient九、max/min十、var 一、attr 返回元素的属性值attr() 理论上能用于所有的 CSS 属性&#xff0c;但目前支持的仅有伪元素的 content 属性&#xff0c;其…

【网络工程师人手必备的常用网络命令合集,整理收藏!】

在计算机网络中经常要对网络进行管理&#xff0c;测试&#xff0c;这时就要用到网络命令。今天就为大家整理了一些网络工程师必备的一些常用网络命令合集&#xff0c;建议收藏后观看哦&#xff01; ping命令 ping是个使用频率极高的实用程序&#xff0c;主要用于确定网络的连…

Linux Shell if 使用参考

if 参考 与许多其他语言一样&#xff0c;PowerShell 提供了用于在脚本中有条件地执行代码的语句。 其中一个语句是 If 语句。 今天&#xff0c;我们将深入探讨 PowerShell 中最基本的命令之一。 案例使用解释&#xff0c;以下是部署virtualbox使用if参考解释 if 判断检查系统…

创新灵感来源于用户实践,TDengine 首次公开四项专利申请

好消息&#xff01;好消息&#xff01; 美国专利局来电 TDengine又有一个新专利证书下来啦&#xff01; 这一专利名为 “一种时序数据库表结构改变处理方法” 做技术创新我们真的是认真的~ 话不多说&#xff0c;给大家上图展示一下 我们都知道&#xff0c;在当下这样一个…

前端性能测试Lighthouse的使用

Lighthouse是一个开源项目&#xff0c;提高网页的质量&#xff0c;生成的测试报告会提供优化方案&#xff0c;以来提高网页的性能。重点就是关注优化建议 官方介绍Lighthouse的地址&#xff1a;https://developer.chrome.com/docs/lighthouse/ 一、使用方法 1、使用谷歌开发者…

Redis数据类型-Set

一. 概述 首先我们来看看Redis中的Set类型有什么特点。 与List相比&#xff0c;Set集合中的元素不允许重复&#xff0c;一个集合中最多可以对应2^32-1(4294967295)个元素。 Set除了可以执行增删改查的命令之外&#xff0c;还支持交集、并集、差集等的计算。 二. 基本命令 对我们…

当Python遇上异步编程:实现高效、快速的程序运行!

前言 同步/异步的概念&#xff1a; 同步是指完成事务的逻辑&#xff0c;先执行第一个事务&#xff0c;如果阻塞了&#xff0c;会一直等待&#xff0c;直到这个事务完成&#xff0c;再执行第二个事务&#xff0c;顺序执行 异步是和同步相对的&#xff0c;异步是指在处理调用这…

单片机的几种ota内存分区表介绍

前言 在做项目时&#xff0c;现在越来越多被要求单片机要支持升级功能。需求变化快&#xff0c;固件要不断支持新的功能&#xff0c;手动人工去烧固件越来越显得麻烦&#xff0c;已经操作成本高。 典型的方式是通过单片机外接的蓝牙、wifi等无线模块&#xff0c;或者通过单片…

如何创建UE5插件?

UE5 插件开发指南 前言1.0.打开插件窗口1.1.打开新建插件窗口1.2.填写新插件信息1.3.查看引擎自动生成的插件内容前言 首先,笔者默认读者已经知道如何安装UE5虚幻引擎了,并且也会编辑器的一些基本操作,那么这里省略了:如何注册Epic Games账号?如何安装UE5引擎?如何安装C++相…

基于SpringBoot的完成SSM整合项目开发

整合第三方技术 1. 整合JUnit问题导入1.1 Spring整合JUnit&#xff08;复习&#xff09;1.2 SpringBoot整合JUnit 2. 基于SpringBoot实现SSM整合问题导入2.1 Spring整合MyBatis&#xff08;复习&#xff09;2.2 SpringBoot整合MyBatis2.3 案例-SpringBoot实现ssm整合 1. 整合JU…

Maven多环境配置与使用、跳过测试的三种方法

文章目录 1 多环境开发步骤1:父工程配置多个环境,并指定默认激活环境步骤2:执行安装查看env_dep环境是否生效步骤3:切换默认环境为生产环境步骤4:执行安装并查看env_pro环境是否生效步骤5:命令行实现环境切换步骤6:执行安装并查看env_test环境是否生效 2 跳过测试方式1:IDEA工具…

机器学习之滤波入门

滤波的基本概念&#xff1a; 滤波是一种信号处理技术。在机器学习中&#xff0c;滤波通常指的是对输入信号进行加工&#xff0c;以消除噪声、平滑信号或突出特定频率范围的信号 简言之:加工输入,达到理想信号。 用生活的例子来解释: 假设你正在听一首音乐&#xff0c;但是在你的…

UML中的assembly关系

UML中的assembly关系 1.什么是Assembly关系 在UML&#xff08;统一建模语言&#xff09;中&#xff0c;"assembly"&#xff08;组装&#xff09;是一种表示组件之间关系的关联关系。组件是系统中可替换和独立的模块&#xff0c;可以通过组装来构建更大的系统。 当一…

零基础入门网络安全必看书单(附电子书籍+配套资料)

学习的方法有很多种&#xff0c;看书就是一种不错的方法&#xff0c;但为什么总有人说&#xff1a;“看书是学不会技术的”。 其实就是书籍没选对&#xff0c;看的书不好&#xff0c;你学不下去是很正常的。 一本好书其实不亚于一套好的视频教程&#xff0c;尤其是经典的好书…

中间件(一)

中间件 1. 概念1.1 为什么要使用中间件&#xff1f;1.2 中间件定义及分类 2. 主要分类2.1 事务式中间件2.2 过程式中间件2.3 面向消息的中间件2.4 面向对象中间件2.5 Web应用服务器2.6 数据库中间件2.7 其他 3. 常用的中间件 1. 概念 中间件&#xff08;Middleware&#xff09…

BigDecimal 类型的使用

目录 一、前言 二、BigDecimal构造方法 二、BigDecimal参与运算 2.1定义初始值 2.2计算 2.3比较大小 2.4BigDecimal取其中最大、最小值、绝对值、相反数&#xff1a; 2.5补充 2.6、java中 BigDecimal类型的可以转换到double类型&#xff1a; 三、BigDecimal格式化、小…

小白白也能学会的 PyQt 教程 —— 图像类及图像相关基础类介绍

文章目录 〇、前言一、PyQt 中的图像类1、图像类简介2、图像类转换① 常用类转换&#xff08;QPixmap、QImage、QIcon&#xff09;② QBitmap、QBrush、QPen 转换为 QPixmap 或 QImage③ QByteArray 与 QPixmap、QImage 的互转④ numpy 与 QImage 互转 二、图像显示组件1、使用…

DNDC模型在土地利用变化、未来气候变化下的建模方法及温室气体时空动态模拟实践技术

DNDC模型讲解 1.1 碳循环模型简介 1.2 DNDC模型原理 1.3 DNDC下载与安装 1.4 DNDC注意事项 ​ DNDC初步操作 2.1 DNDC界面介绍 2.2 DNDC数据及格式 2.3 DNDC点尺度模拟 2.4 DNDC区域尺度模拟 2.5 DNDC结果分析 ​ DNDC气象数据制备 3.1 数据制备中的遥感和GIS技术 3…