C底层 函数栈帧

文章目录

一,什么是寄存器

二,栈和帧


前言

我们在学习c语言程序的时候,是不是有很多的疑问,如

1,为什么形参不可以改变实参

2,为什么我们编写程序的时候会出现烫烫烫......这个乱码

3,那些局部变量和全局变量为什么是全局变量先在程序中出现,局部变量在后面出现

4,为什么会出现栈溢出错误,栈的大小是怎么弄出来的

5,为什么文件的查找数据需要用流,但是printf和scanf也是寻找数据,为什么不用设置流,难道提前设置好了?

等等一系列的问题,接下来我们就要来学习函数栈帧来知道这些问题


一.什么是寄存器

具有存储功能的硬件

在计算机中,具有的存储功能的硬件有哪些呢?

硬盘 --> 内存 --> 高速缓存 --> 寄存器

(从左到右)

访问的速度和缓存的速度是在增加的

                      容量的大小是在减少的

                      价格的大小是在增加的

如果我们考虑外部的存储的话就是这样的

磁带,光盘 --> 硬盘 --> 内存 --> 高速缓存 --> 寄存器(规律和上面的是一样的)

(这里我们生活中所用的u盘其实就是硬盘,只不过把他取出来了)


寄存器

存储的空间只有4Byte为的存储空间(这里说的是32位寄存器,因为很广泛)访问速度也是最快的

那为什么寄存器的访问速度是最快的呢?

因为寄存器是集成在cpu上面的,与内存不同,它是一个独立的空间


寄存器的分类

(寄存器中的E其实是extend的英文缩写,表示把16位寄存器扩展到32位寄存器,没有带E的就是16位寄存器,带E的就是32位,(前面这种在x86框架情况是对的,在其他框架不适用)如在x86-64架构(也称为AMD64)中,寄存器被扩展到64位,如RAX, RBX, RCX, RDX等,这些寄存器可以访问其32位和16位部分,我们要考虑框架和文件)

一般的寄存器:EAX  EBX  ECX  EDX  

ax:累积暂存器    bx:基底暂存器  cx:计数暂存器    dx:资料暂存器

索引寄存器:ESI   EDI

si:来源索引暂存器   di:目的暂存器

堆叠基底寄存器:ESP   EBP

sp堆叠指标暂存器  dp:基底指标暂存器


寄存器的用途

每个寄存器都是有自己各自的专长与特别之处:

一般寄存器

1.EAX(A:accumulation 积累  /   accumulate 计算)

1,为“累加器”,进行加法,减法,乘法,除法运算时,被当做累加器使用(体现出加法的原理)

(为整数与浮点数计算的核心寄存器之一)

2,用于保存计算的结果和数据

2.EBX (B:base 基本)

1,用于保存基地址的信息,常用于访问内存的数据与元素

2,用于保存指针和地址信息,方便与其他的内存地址进行运算

3.ECX(C:counter  计数) 

1,通常被用为计数器,放到循环与迭代操作,在循环里面ECX是可以用于保存循环次数的,然后自己递减,直到0就停止

4.EDX(D:data  数据)

1,保存数据和计算结果的临时存储

2,用于存储整数除法的余数

总结一下一般寄存器

A是计算,所以是作为四则运算的寄存器(累加器),还有一个额外的功能存储数据和结果

B是基本,所以是保存基地址,用于访问里面的数据与元素,因为存的是地址,所以就很方便利用地址运算

C是计数,所以一般存储循环的条件值,然后自己会自己递减到0然后进行结束

D是数据,这里就是存储数据的,所以可以存储数据和计算结果还有整数的乘法和除法的余数

索引暂存器

1.ESI(S:source 源头)

1,这里主要存储指向源数据的指针和索引,它经常与字符串一起使用,指示要操作的字符串的首地址

(这里的“源数据”指的是在执行某些指令时,需要从中读取数据的内存位置或数据结构)

2.EDI(D:destination目的地)

1,这里存储目标数据的指针和索引,通常指向目标字符串的起始位置,以指示存储的位置

总结一下索引寄存器:

ESI(s 源头)这里是存储指向源数据的开头,以便于可以方便操作这个数据

EDI(d 目标)这里是存储目标数据的开头,方便提示这个存储这个目标数据的位置

堆叠,基底暂存器

1.EBP(B  基底)

1,存储堆栈帧的基地址的指针(很重要)

2,在函数调用的和返回的过程中,主要用于维护栈帧的上下文的数据信息,以便正常访问局部变量,传递参数和保存返回地址

2.ESP(S stack 栈(顶))

1,存储堆栈帧的栈顶的指针(很重要)

2,在函数调用的和返回的过程中,主要用于管理栈帧的内存,当压入栈的数据越多,ESP也会相对移动,可以理解为减少

3.EIP(I  instruction)

1,存储下一条需要进行的指令,cpu根据EIP来跟踪程序执行的流程,执行完自动更新EIP指向下一个指令

总结一下堆叠基底暂存器

EBP(B 基底)主要储存基底的地址,所以可以很好管理这些数据

ESP(S 栈(顶))主要储存栈顶的地址,所以可以根据栈顶指针的移动来管理栈的内存

EIP (I(交互))主要是存储下一个指令,方便后续的程序的进行,(可以理解为是为了进行交互)


二,栈和帧

1,栈是什么呢?

数据依次存入栈中,去元素的时候,最先放入的元素最后拿出来,最后放入的元素最先拿出来,这个就是栈

(可以理解为现实生活中的放东西与取东西)

2,函数栈帧的概念

在计算机科学中有这么一个概念,它是指在调用函数的时候,系统为函数调用创建一块内存区域,这块内存区域存储了函数的局部变量,函数的参数,返回地址等信息

这个时候,ESP和EBP是会去维护这个函数的空间,在函数运行的时候,ESP栈顶指针指向栈的头部,EBP指向栈的底部(假设我们整一个main函数)

7ed7c00afcf24561977fc701162bb0a4.png

这个就是我们运行到main函数的时候,所形成的栈帧

3,main函数的压栈过程

在运行调试程序的过程中,我们可以调用堆栈时发现,main函数其实也是被别的函数调用的

 分别是_tmainCRTStartupmainStarup函数,调用的逻辑顺序为

mainStatrup  -->  _tmainCRTStartup  -->  main

这个是mainStartup压栈

0a56747b22dc4111a6118022fbac4b2e.png

这个是_tmainCRTStartup压栈

7f72d0f2fd0f457c9ff9d525f987f88f.png

mainStarup函数:非Unicode版本的例程,它负责main函数的初始化

_tmainCRTStartup函数:Unicode版本的例程,他也是负责main函数的初始化,但是它是支持Unicode字符的初始化

Unicode的大概理解:把我们的文字组合起来让计算机认识并表示出来

初始化:1,环境的设置:全局变量的初始化等...   2,命令行参数的解析  3,I/O流的初始化(这个时候我们才可以用到这个printf和scanf的函数输入输出)4信号处理  5,其他系统的初始化

d5e5e015ab6e49af9f051dc2d585a0ad.png

这个可以理解很多问题了,函数的压栈就是这样

三,样例程序的压栈 

#include<stdio.h>
int add(int x,int y) 
{int z = 0;z = x + y;return z;
}
int main()
{int a = 10;int b = 10;int c = 0;c=add(a, b);printf("%d", c);return 0;
}

1,main函数的构造

(以汇编语言讲述) 

常见的汇编语言(这个是截取一个学长的图片)

010a3ec95b364ab88dea5eb747837091.png

这个是我们main函数前面还没有调用add函数时候的汇编语言 

2839df15461243fe909ec27047729f46.png


 第一步:e232801017a947a7a6920464f0ec7ae4.png

push的作用是把这个东西压入栈中

这里的意思是把ebp这三个压入栈中

图示(有两种情况,会是那种呢?)这里的那个ebp下面的空间是_tmainCRTStartup

4212dc4990874a9686950f1f45cb5e4f.png

由于2019的vs不可以监视到edp的改变,我们可以通过这个来看,来判断是左图还是右图

我们来监视esp的值是多少0843f2ad0789437881360f5795060771.png 

然后打开内存块区寻找ebp的地址(记住这里的ebp不是ebp栈底指针,而是压入到栈的寄存器,一定不要搞混了)我们去内存块区寻找一下这个ebp的地址

22b5b8b1c0f6471ebaf24904665d5468.png 这里看到地址为0x0137FA34   我们把这个20445748十进制转换一下,换成十六进制看看是否相同

345aad786f0d40c29ea0aabc1fe324b5.png 由于大小端的问题,所以这个是倒着存的,如果想知道为什么倒着存储可以去了解一下大小端,这里我们可以观察到这个esp栈顶指针指向的地址就是ebp上面,右图是对的,所以我们每当我们压入栈数据的时候,这个esp是实时进行变换的


第二步:0b3d74088ad34a2083d59f047944d122.png

mov的作用是把后者赋值给前者

所以这里的作用是把esp的地址赋给ebp那这里的图是什么样的呢?

b2e748ebcc8d451ca566ffe0b2086660.png


第三步:7f19208d8acd491c9312c941a649d39a.png 

sub的英文的意思是减去的意思前者减去后者这么多

(注:我们下面是高地址,上面为低地址)

在这里是向上增长0E4h(228)这么多的空间(这里的h是十六进制的后缀 )

b1400a7d3e654e518936c99305e58b11.png

 由于vs2019无法观察这个过程,所以建议读者可以去寻找vs2013去学习,这样可以更加直观,所以这里就是在创建一个空间 


第四步:16321fd5be8d42cd8aed87a290dfbb70.png 

这里的ebx,esi,edi都是非易失寄存器(什么事非易失寄存器呢?就是在电源关闭的时候,也可可以保存其中的内容)那么我们怎么画这个时候的图呢?要记得我们在操作的时候,这个esp栈顶指针是会改变的

81444c560aeb4019888797134a45b44c.png

这个操作就是把这几个寄存器压入栈里面去


 第五步:5c6fb572740145c2bcbc97a3cb181974.png

lea全名是lead effective address加载有效地址

从此处正式加载当前函数的有效栈区域

这里是把ebp-24h这个地址存放在edi里面(我们来回顾一下edi,在下面)这个是存储一个这个这么大的空间的指针,为了准备一个内存区域用于存储函数的局部变量等的数据,有了这个edi才可以找到这个地方的首地址,才可以确保数据的正常存储

(注:因为这个vs2019这个是根据你写的代码所写的汇编语言,每个编译器都是不一样的,如果你在main函数里面不断写入变量,这个就是会改变的)(其实VS2013真正的写的是[ebp-0E4h]的)

栈开辟之后是不可以改变的,如果超出了栈就会报栈溢出错误,一般来说都是200多M的大小一个函数,这里的操作就是正式的开辟一个空间

EDI(D:destination目的地)

1,这里存储目标数据的指针和索引,通常指向目标字符串的起始位置,以指示存储的位置

我们来看看vs2019变化里面的变量后会怎么样

f724c8b2c6f44330ab2fcfdac3c43fec.png8592040b4abc4f6896e052ea12e451dd.png这个是我们加了变量之后这个变成了ebp-48h了,所以可看到不同的编译器所编写的栈帧都是不一样的


 第六步:085c09cbf6c647f4834ff2b5df6de130.png

这里就是把9赋值给ecx寄存器,0CCCCCCCCh赋值给eax中


第七步:636c23866dac4143b50680879b4ef2f6.png 

我们把这个代码拆开来理解:

1,rep这个是一个前缀指令,用于重复执行紧跟其后面的指令(直到ecx为0)

2,stos(全名store  String存储字符串)

把eax的内容赋值到edi所指向的地址中,并把edi递增,逐步的把这个全部填满这个空间

3,dword(全名double word指4个字节,word是指2个字节)

4,ptr  这个是操作数的大小提示符号,比如这里就是告诉编译器,这个是4个字节4个字节输入,顺便告诉编译器,接下来是按照特定的字符大小进行操作的,比如这里的4个字节

5,es:这个是寄存器的前缀,用于指定内存操作

6,[edi]  这个是指是edi这个指向的内存

总体来说,就是每次想edi指向的地址一4个字节的大小不断地把eax存储的值传入进去

那么这里的ccccccccc是什么呢?我们之前不是会遇到烫烫烫这一长串的代码嘛?其实就是这个ccccccc弄出来的,比如变量为初始化,打出的乱码就是烫烫烫,这个烫烫烫实质就是cccccc

8507a7576f104b1c92d02e3c2b125a57.png

程序走到了这里,main函数的帧栈正式开辟成功,这个是有esp和edp形成的区域为一个函数的作用域接下来就是执行main函数内部的东西了


2,生成局部量 

3d7f9d05c9d94f88ab92729237167575.png

这个我们以a为例子:

0Ah其实就是把十六机制转换成十进制,这个就是10的意思,然后这个后面就是把0Ah赋给a这个地址,其他都是一样的

f6edb88b211a4658be3f3076088836fc.png


3,main函数的总结

接下来呢,我们main函数里面的就基本结束了,后面就是add函数的了,我们来总结一下这个main函数是怎么操作的

1,首先就是把一个ebp压入栈中,然后ebp这个是用来代表基地址的或者就是说存放了基地址

2,我们利用edp这个指向esp的位置,是我们这个ebp进行调位置,指向main的基地址

3,我们利用esp这个减去一个数值使esp来想上移动,然后就是给main函数一个预留一个空间

4,我们利用edi的赋值,正式把这个地址赋值给edi,可以把更好的寻找到这个空间的首地址,所以就是相当于正式开辟了一个内存空间,这个空间大小就是我们所预留的空间大小

5,然后对于这个空间里面进行初始化,把这里面填满c这个东西然后给ecx传值

6,对于局部变量的生成利用mov这个指令


接下里就是add这个函数的分析了

4,调用函数与传参

b4b35acb821347f3937e148d92c61424.png


7687d3e0e4dd4875ad98773414f6fe9b.png前面四行代码 

前面两行:显然是把b的地址的值通过mov的指令”拷贝”到eax里面去,(这个mov是其实是转移的意思,也可以理解为拷贝的意思)然后把eax压入到栈中,

后面两行:显然也是把a的地址的值通过mov的指令“拷贝”到ecx里面去,然后就把ecx压入到栈里面去

我们来画一下这个图示:

5c5b04d990e34db9b8eba8fbb192c529.png

为什么我们要有这四行指令,这个其实就是把形参压入栈里面去了,这里我们就可以了解到,其实形参和实参是处于两个独立的空间的,所以我们就可以知道为什么形参的改变不了实参了,答案就在这里(所以这四行代码就是建立形参用的) 


一到四行是为形参做准备,那这样的压入栈中真的可以把参数传入到函数里面吗?调用的函数该怎么使用我们的参数? 我们继续往下看

第五行c3bbcda244e2460fb1772f497e9a6718.png

call指令:这里其实就是一个转移指令符,转移到另外一个地方去,同时也是为了完成转移后完成原区域的下一个指令,那他是怎么实现这个功能的呢?我们继续详细了解,call指令时把我们下一个指令压入栈中,然后这样的话就可以实现转移后可以返回到原地

(简单来说:原地插个眼后传送去支援,最后还可以返回到线上,做到有来有回)

我们来看看真的是这样嘛,由于vs2019是不支持看这个的,所以你们可以下载vs2013点击F11,然后就可以看到类似于这个的声明 

0d725c64da354c10893b0960c318b30f.png

这个就是对于add的一个声明,然后这里有个jmp的指令,这里就是跳入add函数的意思,为转换的操作,我前面画的那个方框是那个地址,应该是call后面的那个地址d62807f42dd54960b4833ef138dcc3f7.png

应该是相同的(这个我是找了别的图,所以不同,因为vs2019弄不出来,我在网上找了一个这个指令,就是想告诉读者有这个操作) 接下来就是正式跳转到add函数了


5,Add函数

add函数的创建:

a8c9adf17dd74cdeafc9ca7b6ddc7112.png

我们先来看这个,这个是不是似曾相识,没错跟我们创建main函数的方式一模一样,读者可以尝试自己去解读一下,这样可以让自己的形象更加深刻,我把答案写到下面了

09bf8a821f904ace8caf3cceb23d6551.png

我们来看看现在的栈帧的图该怎么画

160cfb842c0e4de4ac61d95024dd3db8.png

变量的生成    运算:

dff6142d2d6a48f4be8e6ec683abdb99.png

这个局部变量的形成是跟前面一样的,这里就不多讲了,我来看看后面的 

e4971c08dac9437aa0a81b65b3f9c1b8.png

第一句是把形参x放入到eax中,因为eax的用处有可以用与加法,然后第二句再把这个y放入到这个eax中,然后根据add指令执行相加,我们就的道了结果,然后再把eax的值拷贝给z这个变量,这个z里面去,这个就是运算(形参的压入顺序是按照从左往右的根据你设置的函数)

函数的返回值和函数返回与销毁的实现:

我们按照前面所学的,x,y,z都是在函数调用完会被销毁的

问题1:我们该怎么获取这个返回值?

问题2:esp栈顶指针和ebp栈底指针该何去何从?

7fb0cbd6251d422c8cc19c8c287126a8.png

我们先来看这个return这个代码

这里是把z的值存储到eax中,因为我们知道eax的一个用处是存储数据和运算结果,把他临时放入到eax中就可以把值返回了,第一个问题就迎刃而解了

(当然值超出了eax的范围,就要用到其他寄存器存储了,比如esi等其余寄存器存储) 

62124c865ac44bc3ac8c959875512f86.png

我们再来看看后面的销毁与返回怎么实现的呢我们来看这个指令

5548ff8ed0224abc9f514e320aa20cab.png

pop指令指跳出栈,将元素弹出栈以此释放掉

这个是把是三个非易使寄存器给弹出去,释放掉他们三个(注意这个esp栈顶指针的位置是会变化的)

(这里是弹出三个寄存器)1ce905684bb9482e9a4794299454aa7f.png

这个的用处,栈不是弹出来了那三个寄存器嘛,然后就要收缩调整栈,你看0CCh不正是我们之前所弄出来的空间大小嘛

(这里是栈的收缩调整) 

2c7d54ad044244a488b8695caaea5de5.png

这两行指令其实就是检查是否有栈溢出的哪些错误

cmp这个指令是比较两个

比较基指针EBP和栈指针ESP的值。这通常用于检查栈是否正确对齐,或者在调试时检查栈是否被破坏。

call这一段 

 调用运行时检查函数  __RTC_CheckEsp  ,这个函数可能是用于检查栈溢出或栈保护的。  051244h  是该函数在内存中的地址。这个调用可能是由编译器插入的,用于在运行时检查栈指针是否在函数调用后仍然有效,以防止栈溢出攻击或检测栈损坏

(这两个是检查安全性的)

5c7f4002d73f4218960cfc917a5bdc40.png

我们来看后面的指令

第一行就是把ebp的值赋给esp,然后ebp会读取地址,然后转移到之前main函数的基地址,之后再让ebp读取之前的那个地址,这里的pop是pop另外一个功能,是读取数据的功能,实现了这个esp和ebp的转移

459e59a9e002400ab08d221163fcb3f0.png根据这个转移,最后把这个弹出即可,然后就可以跟着下一个指令了

这里的ret是把栈顶的字节安远出栈,然后交给EIP来处理,这样就可以紧接着这个后面程序的执行即可

我来总结一下add函数的过程:

1,我们先进入函数的调用,先把形参压入到栈里面,然后利用call进入到那个函数的声明的地址哪里并且把下一个指令压入到栈里面去,然后再利用jmp跳入到那个函数里面

2,我们在把局部变量弄出来,然后利用add和eax这两个弄西进行运算,最后赋值给z

3,然后把z的值暂存储在eax中

4,运行返回时,我们就把三个寄存器弹出去,然后ebp会赋给esp,ebp会读取之前的ebp地址进行跳转,然后就可以实现这个esp和edp返回原位置

5,利用ret来实现后面的程序即可


总结

上面的文章里面都有每小段的总结,我们可以根据这些可以解决很多问题

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

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

相关文章

全桥LLC变换器原理及MATLAB仿真模型

“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 主电路拓扑 全桥LLC 谐振变换器主电路拓扑结构图。图中S1 &#xff5e; S4为功率开关管&#xff0c; D1 &#xff5e; D4为功率开关管的体二极管&#xff0c; C1 &#xff5e; C4 为功率开关管的寄生电容。谐振电感r…

TavilySearchResults报错

报错 pydantic_core._pydantic_core.ValidationError: 1 validation error for TavilySearchAPIWrapper Value error, Did not find tavily_api_key, please add an environment variable TAVILY_API_KEY which contains it, or pass tavily_api_key as a named parameter. …

物料理解笔记·蓝白段子线·端子线座子焊接反了怎么处理!!!

目录 蓝白端子排线 端子线座子焊接错了怎么办 端子线如何拆线 编写不易&#xff0c;请勿搬运&#xff0c;仅供学习&#xff0c;感谢理解 蓝白端子排线 蓝白端子排线&#xff0c;这种端子线常用与编码电机的接线&#xff0c;或者在板子上通过提供段子线的接口&#xff0c;通…

shell(8)until循环以及函数基本创建

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

51c自动驾驶~合集35

我自己的原文哦~ https://blog.51cto.com/whaosoft/12206500 #纯视觉方案的智驾在大雾天还能用吗&#xff1f; 碰上大雾天气&#xff0c;纯视觉方案是如何识别车辆和障碍物的呢&#xff1f; 如果真的是纯纯的&#xff0c;特头铁的那种纯视觉方案的话。 可以简单粗暴的理解为…

【ArcGIS Pro实操第11期】经纬度数据转化成平面坐标数据

经纬度数据转化成平面坐标数据 数据准备ArcGIS操作步骤-投影转换为 Sinusoidal1 投影2 计算几何Python 示例 另&#xff1a;Sinusoidal (World) 和 Sinusoidal (Sphere) 的主要区别参考 数据准备 数据投影&#xff1a; 目标投影&#xff1a;与MODIS数据相同&#xff08;Sinu…

【Leecode】Leecode刷题之路第62天之不同路径

题目出处 62-不同路径-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 62-不同路径-官方解法 方法1&#xff1a;动态规划 思路&#xff1a; 代码示例&#xff1a;&#xff08;Java&…

如何写一份优质技术文档

作者简介&#xff1a; 本文作者拥有区块链创新专利30&#xff0c;是元宇宙标准化工作组成员、香港web3标准工作组成员&#xff0c;参与编写《数据资产确权与交易安全评价标准》、《链接元宇宙&#xff1a;应用与实践》、《香港Web3.0标准化白皮书》等标准&#xff0c;下面提供…

【Git】Git 完全指南:从入门到精通

Git 完全指南&#xff1a;从入门到精通 Git 是现代软件开发中最重要的版本控制工具之一&#xff0c;它帮助开发者高效地管理项目&#xff0c;支持分布式协作和版本控制。无论是个人项目还是团队开发&#xff0c;Git 都能提供强大的功能来跟踪、管理代码变更&#xff0c;并保障…

系统手势导航-虚拟导航切换

问题讨论-需求场景 何为手势和物理按键、虚拟导航 Android11 开始支持了手势操作&#xff0c;如大家目前手机基本上都是手势操作形式&#xff1b;早期都是物理按键或者虚拟按键的操作。 手势导航和虚拟导航如何选择 系统层面&#xff1a;设置->系统->手势->手势切…

初识java(4)

今天给大家分享一下java中内置类型定义时的一些要点&#xff0c;我已经整理成笔记&#xff0c;现在分享给大家。 整型变量: 注:在定义int变量时,所赋值不能超过int的范围; 了 intd:1234567890127411编译时报错,初值超过胃int 当你赋值的过而值大于这个变量能够保存的最大值…

Web 毕设篇-适合小白、初级入门练手的 Spring Boot Web 毕业设计项目:电影院后台管理系统(前后端源码 + 数据库 sql 脚本)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 项目介绍 2.0 用户登录功能 3.0 用户管理功能 4.0 影院管理功能 5.0 电影管理功能 6.0 影厅管理功能 7.0 电影排片管理功能 8.0 用户评论管理功能 9.0 用户购票功…

蓝桥杯嵌入式入门指南-ADC【6】

tips:可以不用开启过采样 adc读取函数 float adc_read(ADC_HandleTypeDef *hadc) {uint16_t adc_val;float adc_f;HAL_ADC_Start (hadc);adc_val HAL_ADC_GetValue(hadc);adc_f adc_val*3.3f/4096.0f;return adc_f; }setup()初始化 HAL_ADCEx_Calibration_Start(&hadc2 …

【头歌实训:递归实现斐波那契数列】

头歌实训&#xff1a;递归实现斐波那契数列 文章目录 任务描述相关知识递归相关知识递归举例何时使用递归定义是递归的数据结构是递归的问题的求解方法是递归的 编程要求测试说明源代码&#xff1a; 任务描述 本关任务&#xff1a;递归求解斐波那契数列。 相关知识 为了完成…

【CSP CCF记录】201803-2第13次认证 碰撞的小球

题目 样例输入1 3 10 5 4 6 8 样例输出1 7 9 9 样例输入2 10 22 30 14 12 16 6 10 2 8 20 18 4 样例输出2 6 6 8 2 4 0 4 12 10 2 思路 梳理题意&#xff0c;本题主要考虑三种情况&#xff1a; 1.小球正常运动 2.小球抵达线段两段 3.两个小球在线段某位置相撞 前两种情况很…

ML 系列:第 35 节 - 机器学习中的数据可视化

ML 系列&#xff1a;第 35 天 - 机器学习中的数据可视化 文章目录 一、说明二、数据可视化2.1 直方图2.2 箱线图2.3 散点图2.4 条形图2.5 线图2.6 热图 三、结尾 一、说明 描述性统计和数据可视化是理解和解释机器学习数据的基础。它们有助于总结和直观地呈现数据&#xff0c…

数字化转型背景下,高职院校计算机网络应用的革新策略

在当今信息化时代&#xff0c;计算机网络已经成为高职院校教育不可或缺的一部分&#xff0c;它不仅极大地丰富了教育资源&#xff0c;提高了交流的便捷性&#xff0c;还催生了多样化的教学模式。对于高职院校来说&#xff0c;加强计算机网络应用的建设不仅是顺应时代潮流的必然…

【C/C++】数据库链接入门教程:从零开始的详细指南!MySQL集成与操作

文章目录 环境配置&#xff1a;搭建开发环境的基础步骤2.1 安装MySQL数据库2.2 配置C/C开发环境2.3 下载并安装MySQL Connector/C 基础操作&#xff1a;实现C/C与MySQL的基本交互3.1 建立数据库连接3.2 执行SQL语句3.3 处理查询结果 进阶技巧&#xff1a;提升数据库操作效率与安…

漫谈推理谬误——错误因果

相关文章 漫谈推理谬误——错误假设-CSDN博客文章浏览阅读736次&#xff0c;点赞22次&#xff0c;收藏3次。在日常生活中&#xff0c;我们会面临各种逻辑推理&#xff0c;有些看起来一目了然&#xff0c;有些非常的科学严谨&#xff0c;但也有很多似是而非&#xff0c;隐藏了陷…

redis中的哨兵

redis中的哨兵 一、哨兵机制的概念二、redis哨兵的部署2.1 docker的安装2.2 编排redis主从节点2.3 配置哨兵节点 三、redis哨兵的选举机制3.1 redis-master宕机之后的情况3.2 重启redis-master后的情况 四、redis哨兵机制的原理4.1主观下线4.2客观下线4.3选举leader节点4.4选出…