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

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来实现后面的程序即可


总结

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

 

相关文章:

C底层 函数栈帧

文章目录 一&#xff0c;什么是寄存器 二&#xff0c;栈和帧 前言 我们在学习c语言程序的时候&#xff0c;是不是有很多的疑问&#xff0c;如 1&#xff0c;为什么形参不可以改变实参 2&#xff0c;为什么我们编写程序的时候会出现烫烫烫......这个乱码 3&#xff0c;那些局…...

【模块一】kubernetes容器编排进阶业务容器化案例

Kubernetes 实战案例 Kubernetes实战案例-规划(基于nerdctl buildkitdcontainerd构建容器镜像) 业务容器化优势&#xff1a; ① 提高资源利用率、节约部署IT成本。 ② 提高部署效率&#xff0c;基于kubernetes实现微服务的快速部署与交付、容器的批量调度与秒级启动。 ③…...

可视化建模以及UML期末复习篇----相关软件安装

作为一个过来人&#xff0c;我的建议是别过来。 一、可视化建模 <1>定义: 官方&#xff1a;一种使用图形符号来表示系统结构和行为的建模技术。 我&#xff1a;其实说白了就是把工作流程用图形画出来。懂不&#xff1f; <2>作用: 提高理解和分析复杂系统的能力。促…...

Appflyer记录卸载事件

Appflyer官方文档 1.原理 1.AppsFlyer每天向Firebase Cloud Messaging&#xff08;FCM&#xff09;和 Apple Push Notification Services&#xff08;APNS&#xff09;发送一次API请求。 2.然后FCM和APNS会发送一条静默推送消息&#xff0c;用于判断用户设备上是否仍装有相关应…...

JDK17 AbstractQueuedSynchronizer 二 条件队列

条件队列 同步队列中的线程是为了争抢锁&#xff0c;而条件队列中的线程是主动释放锁&#xff0c;挂起自己&#xff0c;等条件满足时被别的线程唤醒&#xff0c;继续工作。 AQS里只有1个同步队列&#xff0c;但可以有多个等待队列&#xff0c;每个等待队列对应一个ConditionO…...

8 设计模式之简单工厂模式

设计模式是软件开发中的一套通用解决方案&#xff0c;而简单工厂模式则是最基础、最常用的一种创建型模式。在这篇博客中&#xff0c;我将为大家详细介绍简单工厂模式的概念、优缺点&#xff0c;以及通过一个饮料制作的案例&#xff0c;帮助大家更好地理解和应用这种模式。 一、…...

计算机的错误计算(一百六十九)

摘要 探讨 MATLAB 中一个不动点的计算精度问题。 不动点是一类特殊的循环迭代。它有形式 例1. 已知迭代[1] 计算 显然&#xff0c;每个 均为 0.5 . 下面看看 MATLAB 的计算结果。不妨不用循环语句&#xff0c;直接用算术表达式表示 这时计算结果在如下图片&#xff1a; …...

Android 图形系统之三:SurfaceControl

在 Android 系统中&#xff0c;SurfaceControl 是一个关键的类&#xff0c;用于管理应用窗口和屏幕上的显示内容。它与 SurfaceFlinger 紧密交互&#xff0c;通过 BufferQueue 提供高效的图形缓冲区管理能力。SurfaceControl 是 Android 的显示架构中不可或缺的部分&#xff0c…...

Laravel8.5+微信小程序实现京东商城秒杀方案

一、商品秒杀涉及的知识点 鉴权策略封装掊口访问频次限制小程序设计页面防抖接口调用订单创建事务使用超卖防御 二、订单库存系统方案&#xff08;3种&#xff09; 下单减库存 优点是库存和订单的强一致性&#xff0c;商品不会卖超&#xff0c;但是可能导致恶意下单&#xff…...

Makefile 入门指南:构建自动化编译流程

个人主页&#xff1a;chian-ocean 文章专栏 前言 make 和 Makefile 是编译和构建软件项目时非常常用的工具和文件&#xff0c;它们通常配合使用来自动化项目的编译过程。 make 定义&#xff1a;make 是一个构建自动化工具&#xff0c;用于根据项目文件的依赖关系自动完成编译…...

C#热更原理与HybridCLR

一、Mono的诞生 在Mono之前,C#虽然很好,但是只在windows家族平台上使用,就这点C#与Java就无法比。于是微软公司向ECMA申请将C#作为一种标准。在2001年12月,ECMA发布了ECMA-334 C#语言规范。C#在2003年成为一个ISO标准(ISO/IEC 23270)。意味着只要你遵守CLI(Common Lang…...

里氏替换原则:Java面向对象设计的基石

在面向对象编程&#xff08;OOP&#xff09;中&#xff0c;继承是一个强大的工具&#xff0c;它允许我们创建新的类&#xff08;子类&#xff09;来复用和扩展现有类&#xff08;父类&#xff09;的功能。然而&#xff0c;继承也带来了复杂性&#xff0c;特别是在确保子类能够正…...

恒创科技:服务器操作系统和客户端操作系统之间的区别

客户端操作系统和服务器操作系统是两种不同的操作系统&#xff0c;旨在满足计算机网络环境中的特定目的。虽然每种类型的操作系统在基本功能方面都有一些相似之处&#xff0c;但它们针对不同的用例进行了优化&#xff0c;并具有针对其特定角色量身定制的特定功能。 什么是服务器…...

做异端中的异端 -- Emacs裸奔之路4: 你不需要IDE

确切地说&#xff0c;你不需要在IDE里面编写或者阅读代码。 IDE用于Render资源文件比较合适&#xff0c;但处理文本&#xff0c;并不划算。 这的文本文件&#xff0c;包括源代码&#xff0c;配置文件&#xff0c;文档等非二进制文件。 先说说IDE带的便利: 函数或者变量的自动…...

Unity3d C# 摄像头检测敌方单位(目标层级)并在画面中标注(含源码)

前言 需要实现的功能是通过一个专门的检测摄像头将出现在摄像头画面内的敌方单位检测出来&#xff0c;并通过框选的UI框在画面中标记出来。检测摄像头支持自动检测和手动控制检测&#xff0c;同时需要实现锁定模式&#xff0c;检测到一个敌方单位直接锁定到对象上等功能。 效…...

js 16进制加密

function hexEncode(str) { let hexEncodedStr ‘’; for (let i 0; i < str.length; i) { let charCode str.charCodeAt(i); let hexCode charCode.toString(16).padStart(2, ‘0’); hexEncodedStr ‘\x’ hexCode; } return hexEncodedStr; } // 示例用法 let ori…...

性能测试之压测

1、首先需要提前准备好需要压测的接口地址及对应的接口参数 写好对应的压测接口及对应参数脚本 2、添加线程组&#xff08;根据对应的需求提供的QPS及需要压测的数量如有&#xff09; 如&#xff1a;40个线程&#xff0c;循环次数为永远&#xff08;或者根据自身情况设置循…...

CentOS修改yum.repos.d源,避免“Could not resolve host: mirrorlist.centos.org”错误

1、问题现象 由于CentOS停止维护&#xff0c;mirrorlist.centos.org网站也关闭不可访问。导致CentOS默认配置的yum.repos.d源也不可用&#xff0c;所以执行yum命令会报“Could not resolve host: mirrorlist.centos.org”错误。具体如下&#xff1a; Could not retrieve mirror…...

Python 三目运算实战详解

Python 的三目运算符&#xff08;也称为条件表达式&#xff09;是一种简洁的方式来执行基于条件的赋值或返回值。它的语法类似于其他编程语言中的三元运算符&#xff0c;但有一些细微的不同。在 Python 中&#xff0c;三目运算符的语法如下&#xff1a; value_if_true if cond…...

JVM 性能调优 -- CMS 垃圾回收器 GC 日志分析【Full GC】

前言&#xff1a; 上一篇我们分析了 Minor GC 的发生过程&#xff0c;因为 GC 日志没有按我们预估的思路进行打印&#xff0c;其中打印了 CMS 垃圾回收器的部分日志&#xff0c;本篇我们就来分析一下 CMS 垃圾收集日志。 JVM 系列文章传送门 初识 JVM&#xff08;Java 虚拟机…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...