[内功修炼]函数栈帧的创建与销毁

文章目录

    • 1:什么是函数栈帧
    • 2:理解函数栈帧能解决什么问题呢
    • 3:函数栈帧的创建与销毁的解析
      • 3.1:什么是栈
      • 3.2:认识相关寄存器与汇编指令
        • 相关寄存器
        • 相关汇编指令
      • 3.3 解析函数栈帧的创建和销毁
        • 3.3.1 预备知识
        • 3.3.2 详细解析
          • 一:调用main函数,为main函数开辟函数栈帧
          • First:
            • push前
            • push后
          • Second:
            • mov前
            • mov后
          • Third:
            • sub前
            • sub后
          • Fourth
            • 三次push前
            • 三次push后
          • Fifth:
            • lea前
            • lea后
          • Sixth
            • mov前:
            • mov后:
          • seventh
            • 拷贝前
            • 拷贝后
          • eighth
            • mov前
            • mov后
          • ninth
            • mov前
            • mov后
          • tenth
          • eleventh
            • call前
            • call后
          • twelfth
          • thirteenth
            • push前
            • push后
          • fourteenth
            • mov前
            • mov后
          • fifteenth
            • sub前
            • sub后
          • sixteenth
            • 三次push前
            • 三次push后
          • Seventeenth
            • lea前
            • lea后
          • eighteenth
            • mov前
            • mov后
          • nineteenth
            • 拷贝前
            • 拷贝后
          • twentieth
            • mov前
            • mov后
          • twenty-first
            • Add前
            • Add后
          • Twenty-Second
            • mov前
            • mov后
          • Twenty-Third
          • Twenty-fourth
            • 三次pop前
            • 三次pop后
          • Twenty-fifth
          • Twenty-sixth
            • pop前
            • pop后
          • Twenty-Seventh
          • Twenty-eighth
            • Add前
            • Add后
          • Twenty-ninth
          • mov前
          • mov后
          • Thirtieth
    • 4.:前期问题解答
      • 4.1:局部变量是如何创建的
      • 4.2:为什么局部变量的值不初始化时是随机值?
      • 4.3:函数是怎么传参的?传参的顺序是怎么样的
      • 4.4:形参和实参是什么关系?
      • 4.5: 函数是怎么做的以及函数调用结束后怎么返回的?

嘿嘿,家人们,今天呢咱们来详细讲解函数栈帧的创建与销毁,好啦,废话不多讲,开干!

1:什么是函数栈帧

我们在写C语言代码的时候,经常会把一个独立的功能封装在一个函数中,因此,C程序是以函数为基本单位的,那函数是如何调用的呢?函数的返回值又是如何带回呢?函数的参数又是如何传递的呢?这些都与函数的栈帧有关系。
函数栈帧就是用来函数在调用过程中在程序的调用栈所开辟的空间,这些空间是用来存放:
(1):函数参数和返回值
(2):临时变量(包括函数的非静态区的局部变量以及编译器自动产生的其他临时变量)
(3):保存上下文信息 (包括在函数调用前后需要保持不变的寄存器)。

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

只要理解了函数栈帧,那么如下问题就能很好地理解了,并且更方便我们日后的学习!

(1):局部变量是如何创建的?
(2):为什么局部变量不初始化内容就被赋予随机值?
(3):函数调用时参数是如何传递的?传参的顺序是怎么样的?
(4):函数的形参与实参分别是怎么样实例化的?
(5):函数的返回值是如何带回的?

那么就让我们一起走进函数栈帧的创建与销毁的过程中。

3:函数栈帧的创建与销毁的解析

在解析函数栈帧的创建与销毁之前,首先呢得了解一些预备知识,这样子方便后续的理解。

3.1:什么是栈

栈是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,同时也就没有我们如今看到的所有的计算机语言。
在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先进后出(先入栈的数据后出栈).就好比叠成一叠的书,先叠上去的书在最下面,因此要最后才能取出。
在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。
在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。

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

相关寄存器

eax:通用寄存器,保留临时数据,常用于返回值。
ebx:通用寄存器,保留临时数据。
ebp:栈底寄存器。
esp:栈顶寄存器。
eip:指令寄存器,保存当前指令的下一条指令的地址

相关汇编指令

mov:数据转移指令。
push:数据入栈,同时esp栈顶寄存器也要发生改变。
pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变。
sub:减法命令;
add:加法命令;
call:函数调用, 1. 压入返回地址 2.转入目标函数
jump:通过修改eip,转入目标函数,进行调用。
ret:恢复返回地址,压入eip,类似pop eip命令。

3.3 解析函数栈帧的创建和销毁

3.3.1 预备知识
了解了上面的相关知识后,我们还需要达成一些预备知识才能有效地帮助我们去理解,函数栈帧的创建与销毁。

1.:每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
2.:这块空间的维护是使用了2个寄存器:esp和ebp,ebp记录的是栈底的地址,被称为栈顶指针,esp记录的栈底的地址,被称为栈底指针
3.函数栈帧的创建和销毁过程,在不同的编译器上实现的方法是大同小异,博主在这里使用的是VS2019.建议家人们使用VS2013或者更低版本的编译器,不要使用更高的编译器,越高级的编译器,环境虽然稳定,但不容易进行观察和学习,不同版本的编译器在观察函数栈帧的创建与销毁是有些差异的。

3.3.2 详细解析
了解上面的预备知识后,博主将通过以下这段代码来具体解析函数栈帧的创建和销毁
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int a,int b)
{
	int value = a + b;
	return value;
}

int main()
{
	int a = 10;
	int b = 20;
	int result = Add(a, b);
	printf("%d", result);
	return 0;
}
一:调用main函数,为main函数开辟函数栈帧

在这里插入图片描述
在这里插入图片描述

我们通过F10进行调试,然后进入窗口,点击里面的调用堆栈,我们可以清楚地看到,此时main函数被调用了,但是它是被谁调用的呢?这里博主引用了下vs2013的环境下main函数的调用情况。因为在vs2019的环境是看不到main函数时被哪个函数调用的.

在这里插入图片描述

从这张图片中,我们可以得知,main函数是由一个叫** _tmainCRTStartup函数 所调用,而 _tmainCRTStartup是由mainCRTStartup所调用的**,在之前的预备知识我们讲到过,每一次调用函数都要为函数的开辟一块空间,这块空间就是函数栈帧。博主通过一张如下图片来慢慢解析。

在这里插入图片描述

可能有的uu会有些疑惑,为什么博主不画main函数的栈帧呢,uu们不要着急此时main函数的函数栈帧还没有创建呢,接下来博主将通过反汇编指令来带着大家一步一步地解析。

在这里插入图片描述

首先我们右击鼠标,然后点击转到反汇编,跳到反汇编后,就能看到如下的汇编代码!

在这里插入图片描述

估计大多数uu们看到这段汇编代码都是晕晕的感觉,没关系,博主将一步一步地带着大家去解析!

First:

在这里插入图片描述

首先第一步是进行push,压栈操作,将ebp寄存器的值压入到栈顶,在之前我们了解过,每一次压栈栈顶指针esp指向的地址也会发生变化,此时esp寄存器里头存放的是寄存器ebp的值。uu们如果想详细地了解的话,可以打开调试里面的监视和内存去观看寄存器esp的变化。

push前

在这里插入图片描述

push后

在这里插入图片描述
在这里插入图片描述

通过push前与push后的对比,我们可以清楚地发现,栈顶指针的地址发生了变化,减小了,在之前我们了解过,栈是向下增长的,也就是从高地址到低地址进行增长,那么此时所对应的函数栈帧图如下图。

在这里插入图片描述

Second:

在这里插入图片描述

第二步进行mov:数据移动指令,将寄存器esp的值赋给ebp,那么此时esp与ebp都指向栈顶。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过mov前与move后的对比,我们可以清楚地发现此时ebp与esp的值相等,两个寄存器都指向栈顶,那么此时所对应的函数栈帧图如下图!

在这里插入图片描述

Third:

在这里插入图片描述

第三步执行sub指令,将寄存器栈顶指针的值减去0E4h(16进制),那么此时栈顶指针往低地址走。

sub前

在这里插入图片描述

sub后

在这里插入图片描述

通过对比sub前与sub后,我们会发现此时esp和ebp不再维护最初的空间,而是维护了一块新的空间,我们之前说过,函数每次调用都会在栈区上开辟一块属于自己的函数栈帧,那么此时栈顶指针esp与栈底指针ebp所维护的空间就是为main函数所开辟的函数栈帧,所对应的函数栈帧图如下。

在这里插入图片描述

Fourth

第四步:连续push三次,也就是向栈中压入三个值,分别为ebx,esi,edi。我们之前说过,每进行一次压栈,栈顶指针esp都会向下增长(向低地址增长)。

三次push前

在这里插入图片描述

三次push后

在这里插入图片描述

通过对比三次push前与三次push后,esp的值此时减少了,此时所对应的函数栈帧图如下图

在这里插入图片描述

Fifth:

第五步:执行lea指令,lea指令的全称呼是load effective address(加载有效地址); 将ebp-24h这个地址的值存放到寄存器edi中,我们可以通过监视来观察。

lea前

在这里插入图片描述

lea后

在这里插入图片描述

Sixth

第六步:执行两次mov指令,将9(16进制)与0CCCCCCCCh分别存放到寄存器ecx与eax中。

mov前:

在这里插入图片描述

mov后:

在这里插入图片描述

通过两次mov的对比,我们会发现此时寄存器ecx中存储的值为9,eax中的值为16进制的0xcccccccc。

seventh

第七步:从ebp的位置开始到edi的位置也就是ebp - 24h的位置,将eax中的值拷贝到这两个寄存器所维护的区域,每次拷贝dword(doubleword)双字,一个字占2个字节,双字占四个字节,总共拷贝 ecx次也就是9次。

拷贝前

在这里插入图片描述

拷贝后

在这里插入图片描述

对比拷贝前与拷贝后,我们可以发现,ecx的值从9变为了0,也就是总共拷贝了九次,每次拷贝4个字节!此时所对应的函数栈帧图如下图

在这里插入图片描述

eighth

在这里插入图片描述

第八步,创建局部变量a并且为其赋值,在将0Ah(10)(存储到ebp - 8的位置上,实际上在ebp - 8的位置上就是局部变量a。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过mov前后的对比,此时在ebp-8的位置上从最初的cccccccc变成了0x0000000a也就是10。此时所对应的函数栈帧图如下

在这里插入图片描述

ninth

在这里插入图片描述

创建局部变量b并且为其赋值,在将014h(20)存储到ebp - 14h(ebp - 20)的位置上,实际上在ebp - 14h的位置上就是局部变量b。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过对比mov前与mov后,此时在ebp-14h的位置上从最初的cccccccc变成了0x00000014也就是20。此时所对应的函数栈帧图如下

在这里插入图片描述

tenth

在这里插入图片描述

将ebp - 14h这个位置所存储的四个字节的数据存放到寄存器eax中,然后再进行压栈,接着再将ebp - 8这个位置所存储的四个字节的数据存放到寄存器ecx中,然后再进行亚栈。PS:通过之前的操作我们可以得知,ebp - 14h所存储的值就是局部变量b,ebp - 8这个位置所存储的值就是局部变量a。这几步操作是在调用Add函数前进行传参
此时所对应的函数栈帧图如下。可能家人们对这几步操作还不是特别地理解,没关系,我们先向下看!

在这里插入图片描述

eleventh

在这里插入图片描述

这一步,我们将要执行Call指令即调用函数,在执行Call指令前我们首先记住这个地方的地址,然后这个时候uu们就不要按F10了,按F11,此时我们再观察一下,栈顶指针
的位置

call前

在这里插入图片描述

call后

在这里插入图片描述

在call之前,我们可以观察到,栈顶指针里面存储的是00 00 00 0a也就是20,这是一开始压栈所影响的,在call之后,esp的地址发生了变化,家人们有木有觉得此时栈顶指针esp所存储的内容是不是很熟悉,没错!在调用的同时,会将call指令的下一条指令的地址进行压栈,那么此时所对应的函数栈帧图如下。至于它的意义何在呢?我们先继续往下看!

在这里插入图片描述

twelfth

在这里插入图片描述

执行完第11步后,此时我们再按F11,这时候算是真正来到了Add函数里面,来到Add函数里面,仔细观看一下,前面这一部分,是不是跟main函数的情况大同小异,也就是在为我的Add函数开辟函数栈帧。这里博主再带家人们走一遍步骤.

thirteenth

此时进入了Add函数后,首先进行push,此时将ebp的值进行压栈(这个ebp是main函数的ebp),我们知道ebp为栈底指针,ebp之前一直在维护main函数的函数栈帧,此时对其压栈,就等价于是将main的ebp进行压栈。

push前

在这里插入图片描述

push后

在这里插入图片描述

对比push前与push后,我们可以清楚地发现此时栈顶指针esp指向的地址发生了变化,减小了。此时所对应的函数栈帧图如下。

在这里插入图片描述

fourteenth

第十五步:进行mov操作,将寄存器esp的值赋给ebp,那么此时esp与ebp都指向栈顶。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

对比mov前与mov后,我们可以清楚地看到,此时esp与ebp所指向的地址是一样的,此时所对应的函数栈帧图如下

在这里插入图片描述

fifteenth

此时将执行sub指令,将寄存器栈顶指针的值减去0CCh(16进制),那么此时栈顶指针往低地址走。

sub前

在这里插入图片描述

sub后

在这里插入图片描述

通过过对比sub前与sub后,我们会发现此时esp和ebp不再维护main函数的函数栈帧,而是维护了一块新的栈帧,我们之前说过,函数每次调用都会在栈区上开辟一块属于自己的函数栈帧,那么此时栈顶指针esp与栈底指针ebp所维护的空间就是为Add函数所开辟的函数栈帧,所对应的函数栈帧图如下。

在这里插入图片描述

sixteenth

第16步,连续push三次,也就是向栈中压入三个值,分别为ebx,esi,edi。我们之前说过,每进行一次压栈,栈顶指针esp都会向下增长(向低地址增长)。

三次push前

在这里插入图片描述

三次push后

在这里插入图片描述

通过对比三次push前与三次push后,esp的值此时减少了,此时所对应的函数栈帧图如下图

在这里插入图片描述

Seventeenth

第十七步,执行lea指令,lea指令的全称呼是load effective address(加载有效地址); 将ebp-0Ch这个地址的值存放到寄存器edi中,我们可以再次通过监视来观察。

lea前

在这里插入图片描述

lea后

在这里插入图片描述

eighteenth

第十八步,执行两次mov指令,将3(16进制)与0CCCCCCCCh分别存放到寄存器ecx与eax中。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过两次mov的对比,我们会发现此时寄存器ecx中存储的值为3,eax中的值为16进制的0xcccccccc。

nineteenth

第十九步,从ebp的位置到edi也就是ebp - 0ch的位置开始,将eax中的值拷贝到这两个寄存器所维护的空间,每次拷贝dword(doubleword)字,一个word占两个字节,douleword占四个字节,总共拷贝ecx次也就是3次。

拷贝前

在这里插入图片描述

拷贝后

在这里插入图片描述

对比拷贝前与拷贝后,我们可以发现,ecx的值从3变为了0,也就是总共拷贝了三次,每次拷贝4个字节!此时所对应的函数栈帧图如下图。

在这里插入图片描述

twentieth

在这里插入图片描述

第20步,将ebp + 8这一个位置所指向的值,存放到eax寄存器中,我们可以通过观察函数栈帧图,ebp + 8所指向的值,正好是我Add函数里头形式参数a,没错,就是就形参a的值mov到寄存器eax中!

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过对比mov前与mov后,我们可以发现,此时eax中的值为16进制的 0x00 00 00 0a(即十进制的10)

twenty-first

在这里插入图片描述

第二十一步,执行Add命令,将ebp + 0Ch所指向的值加到eax寄存器中,我们通过观察函数栈帧图可以发现,ebp + 0Ch所指向的值正好是形参b,没错,就是将变量b的值加到eax寄存器中。

Add前

在这里插入图片描述

Add后

在这里插入图片描述

通过对比Add前与Add后,我们可以发现,此时eax中的值为16进制的 0x00 00 00 1e(即十进制的30)

Twenty-Second

在这里插入图片描述

将eax中的值mov到ebp - 8 的位置上,之前的mov与add操作使得寄存器eax中所存储的值为30,也就是说此时ebp - 8的位置所存放的值是30并且由变量value所接收,那么也就是说ebp - 8的位置上存储的就是局部变量value。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过对比mov前与mov后,我们可以发现此时ebp - 8的位置上存储的确实是寄存器eax中的值也就是十六进制的0x 00 00 00 1e(十进制的30),此时所对应的函数栈帧图如下

在这里插入图片描述

Twenty-Third

在这里插入图片描述

第二十三步,将ebp - 8这个地址所指向的值(也就是变量value的值)存放到寄存器eax中,我们知道,寄存器不会随着程序的退出而销毁,与此同时,变量value一会出Add函数后会销毁,因此先存放到寄存器eax中。
在这里插入图片描述

Twenty-fourth

在这里插入图片描述

第二十四步,连续进行三次pop,分别将edi,esi,ebx这三个值从栈顶弹出并且弹到edi寄存器,esi寄存器,ebx寄存器中,同时esp栈顶寄存器也要发生改变,每次弹出esp的值会 + 4。

三次pop前

在这里插入图片描述

三次pop后

在这里插入图片描述

通过对比三次pop前与三次pop后,我们可以发现,此时esp的值加了 12,此时所对应的函数栈帧图如下图。

在这里插入图片描述

Twenty-fifth

在这里插入图片描述

第二十五步:这一步操作,将ebp赋给esp,这一步的操作就是在销毁Add函数的函数栈帧,因为函数调用完了,我的变量value值存放在了eax寄存器,因此此时通过mov操作将ebp的值赋给esp来销毁Add函数的函数栈帧。此时所对应的函数栈帧图如下图。

在这里插入图片描述

在这里插入图片描述

Twenty-sixth

在这里插入图片描述

第二十六步:将栈顶元素弹出到寄存器ebp中,我们可以发现此时的栈顶元素是main函数的ebp的地址值,那么为什么会在这里存放main函数的ebp的地址值呢,是因为随着函数栈帧的销毁,main函数的栈顶是很容易找到的,但是main函数的栈底是难以找到的,因此将main函数的栈底ebp的地址值存储值,当Add函数调用完以后,进行pop,此时ebp就回到了main函数的栈底了。pop的同时栈顶指针esp也会 + 4。
PS:博主在这里经过了多次调试,所以可能call指令的下一条指令的地址值会有些不同,家人们这里要注意一下哦

pop前

在这里插入图片描述

pop后

在这里插入图片描述

通过对比pop前与pop后,我们可以发现此时esp的地址值增加了4,ebp的值也发生了变化,此时所对应的函数栈帧图如下。

在这里插入图片描述

Twenty-Seventh

在这里插入图片描述

第二十七步:执行ret指令,我们仔细观看函数栈帧,此时栈顶上存储着最初调用Add函数的call指令的下一条指令的地址,在执行ret指令的同时此时会在栈顶弹出call指令的下一条指令的地址,那么此时ret指令就是回到执行Add函数时的call指令的下一条地址。**那么家人们可以思考一下,为什么我们要存放call指令的下一条指令的地址呢?其目的是,当调用完函数以后,我还能回来,回来之后还能够从call指令的下一条指令开始执行。**此时所对应的函数栈帧图如下。

在这里插入图片描述

Twenty-eighth

在这里插入图片描述

第二十八步:对esp寄存器执行 + 8的操作,我们观察函数栈帧图,此时在栈中还存在着调用Add函数的形参变量a与形参变量b,Add函数已经调用完了,那么此时既要销毁函数栈帧又要销毁形参,此时这一步操作就是用来销毁形参的,将其还给操作系统。

Add前

在这里插入图片描述

Add后

在这里插入图片描述

通过对比Add前与Add后,我们可以发现,esp的值发生了变化,此时所对应的函数栈帧图如下

在这里插入图片描述

Twenty-ninth

在这里插入图片描述

第二十九步,执行mov操作,将eax中的值mov到ebp - 20h的位置上,我们会发现,刚刚调用完Add函数的结果会存放变量result中,而result变量就是在ebp - 20h的位置上,然后我们再想一下,eax中的值是什么,就是我们在调用Add函数时求出来的和存放到了eax寄存器中,那么也就说此时ebp - 20h位置上的值就是调用Add函数时求出来的和。

mov前

在这里插入图片描述

mov后

在这里插入图片描述

通过对比mov前与mov后,我们可以发现,此时ebp - 20h上的值就是eax寄存器中所存放的值,此时所对应的函数栈帧图如下

在这里插入图片描述

Thirtieth

在这里插入图片描述

剩下的操作就是执行printf函数,将其打印在屏幕上,然后再销毁main函数的函数栈帧,这里博主就不再细讲啦,uu下去以后可以结合博主讲解的Add函数栈帧的销毁过程去理解哦!

4.:前期问题解答

4.1:局部变量是如何创建的

局部变量的创建,首先是为函数开辟好栈帧空间,栈帧空间里头,初始化一部分空间以后,然后再在栈帧空间里头对局部变量进行分配空间。

4.2:为什么局部变量的值不初始化时是随机值?

函数栈帧在开辟好以后会对一部分空间进行初始化,而初始化的值是随机值,这也就是为什么局部变量在不初始化时会是随机值。

4.3:函数是怎么传参的?传参的顺序是怎么样的

当我们要调用函数时,其实在还没调用前,通过不断地push,从右向左开始压栈,压入到栈中,当真正进入到函数里头,通过指针的偏离量来找到形参。

4.4:形参和实参是什么关系?

形参是我们压栈时为其开辟的空间,这块空间与实参的空间是相互独立的,两块空间只是存储的值一致,因此我们才会说形参是实参的一份临时拷贝,形参的改变影响不到实参。

4.5: 函数是怎么做的以及函数调用结束后怎么返回的?

当我们在调用函数的同时即执行call指令时,call指令会将下一条执行指令的地址值压入栈中与此同时又会将上一个函数栈帧中的ebp的地址值也压入栈中,在调用完函数后要返回时,通过pop ebp找到上一个函数的栈顶的地址值,从令esp栈顶指针与ebp栈底指针重新维护这块函数栈帧与此同时会销毁此时调用的Add函数的函数栈帧,然后通过在栈中压入的call指令的下一条指令的地址值跳转到与之对应的位置,令函数返回,返回值则是通过在函数调用完之前事先存放在寄存器中被带回来的。

 好啦,家人们,关于函数栈帧的创建与销毁这块的相关细节知识,博主就讲到这里了,如果uu们觉得博主讲的不错的话,请动动你们滴滴给博主点个赞,你们滴鼓励将成为博主源源不断滴动力!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python3】\u字符与中文字串互转

小水。 encode和decode&#xff1a; str没有decode函数&#xff0c;但对应的有encode函数&#xff0c;该函数作用是转码为bytes对象bytes通过decode函数转换回对应的str对于一些偏激的(可以用过分来形容)的字符串&#xff0c;例如一二三\\u56db\\u4e94\\u516d&#xff0c;是有…

k8s 组件

k8s: kubernets:8个字母省略&#xff0c;就是k8s. 自动部署&#xff0c;自动扩展和管理容器化的应用程序的一个开源系统。 k8s是负责自动化运维管理多个容器化程序的集群&#xff0c;是一个功能强大的容器编排工具。 以分布式和集群化的方式进行容器管理。 1.20面试版本 …

RPC(6):RMI实现RPC

1RMI简介 RMI(Remote Method Invocation) 远程方法调用。 RMI是从JDK1.2推出的功能&#xff0c;它可以实现在一个Java应用中可以像调用本地方法一样调用另一个服务器中Java应用&#xff08;JVM&#xff09;中的内容。 RMI 是Java语言的远程调用&#xff0c;无法实现跨语言。…

小程序真机如何清除订阅数据

在做小程序订阅消息开发的过程中发现&#xff0c;真机上如果是选择了‘总是保持以上选择’&#xff0c;一旦用户授权后&#xff0c;后面就不会再弹出申请改订阅消息的授权弹窗&#xff0c;这对于开发过程中是很不方便的。 曾试过清除缓存&#xff0c;重进小程序也不能清除掉 解…

爬虫反爬之代码混淆,特殊编码,表情编码

不知道你是否见过这样的代码&#xff0c;完全看不懂。 大家好&#xff0c;这一集我们来看一下前端反爬的代码混淆&#xff0c;一般啊我们自己写的前端代码都是直接上传公开的&#xff0c;如果用的不是框架打包出来的代码&#xff0c;就是自己写的js&#xff0c;html文件没有经过…

T-Dongle-S3开发笔记——创建工程

创建Hello world工程 打开命令面板 方法1&#xff1a;查看->命令面板 方法2&#xff1a;按F1 选择ESP-IDF:展示示例项目 创建helloworld 选择串口 选择芯片 至此可以编译下载运行了 运行后打印的信息显示flash只有2M。但是板子上电flash是W25Q32 4MB的吗 16M-bit

SPFA算法总结

知识概览 SPFA算法是Bellman_Ford算法的优化。时间复杂度一般是O(m)&#xff0c;最坏时间复杂度是O(nm)&#xff08;遇到网格图、菊花图&#xff09;&#xff0c;其中n是点数&#xff0c;m是边数。SPFA算法其实是单源最短路限制最小的算法&#xff0c;只要图中没有负环&#xf…

Mongodb基础介绍与应用场景

NoSql 解决方案第二种 Mongodb MongoDB 是一款开源 高性能 无模式的文档型数据库 当然 它是NoSql数据库中的一种 是最像关系型数据库的 非关系型数据库 首先 最需要注意的是 无模式的文档型数据库 这个需要后面我们看到它的数据才能明白 其次是 最像关系型数据库的非关系型数据…

通过three.js玩转车展项目

1.项目搭建 1.1 创建文件夹 mkdir 文件名1.2 初始化package.json npm init -y1.3 安装打包工具并配置相关依赖 npm i parcel -d在package.json中打包路径和指令 1.4 安装three.js npm i three -d2.项目搭建 2.1 新建index.html&#xff0c;并再index.html引入car.js,在…

2023版本QT学习记录 -6- UDP通信之UDP接收端

———————UDP接收端——————— &#x1f384;动图演示 &#x1f384;发送端通信步骤思维导图 &#x1f384;添加组件 QT core gui network&#x1f384;添加头文件 #include "qudpsocket.h"&#x1f384;创建接收对象 QUdpSocket *recvsocket;&…

在VSCode中使用Git教程

文章目录 提交代码操作分支提交远程库拉取代码参考 介绍一下如何在VSCode中使用Git 首先在VSCode中打开一个项目 打开项目后, 点击下图按钮, 可以引入Git 提交代码 点击 &#xff1b;相当于git add. 下面两张图, 第一张表示改文件后的号, 只会add本文件. 第二张图表示这段时…

Jenkins的邮箱配置和插件下载

启动&#xff1a;java -jar jenkins.war 一定在jenkins.war的目录下 进入cmd命令 浏览器输入网址&#xff1a;http://localhost:8080/login?from%2F 账号&#xff1a;admin 密码&#xff1a;123456 安装插件&#xff1a; 插件更新后重启下 配置邮箱账号&#xff1a; 3…

关于PSINS中涉及到的地球参数更新

地球参数相关的更新函数 void CEarth::Update(const CVect3 &pos, const CVect3 &vn, int isMemsgrade) 用到位置以及速度 那么位置和速度用的哪个时刻的&#xff1f; 假如设计算周期为[T,2T] &#xff1b; 解算时刻为2T时刻&#xff0c;那么地球参数用的是哪一时刻的…

【自然语言处理】用Python从文本中删除个人信息-第二部分

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

canvas基础教学

Canvas <canvas>是一个可以使用脚本&#xff08;通常是JavaScript&#xff09;来绘制图形的HTML元素&#xff0c;例如&#xff0c;它可以用于绘制图表、制作图片构图或者制作简单的动画。 本篇博客从一些就基础开始&#xff0c;描述了如何使用<canvas>元素来绘制…

Java基础回顾——多线程

文章目录 介绍创建新线程线程的状态中断线程守护线程线程同步同步方法 死锁wait和notifyReentrantLockconditionReadWriteLockStampedLockSemaphore线程池FutureCompletableFuture 介绍 计算机中&#xff0c;一个任务称为一个进程&#xff0c;某些进程内部还需要同时执行多个子…

excel统计分析——K-S正态性检验

参考资料&#xff1a; 马兴华,张晋昕.数值变量正态性检验常用方法的对比[J].循证医学,2014,14(02):123-128 统计推断——正态性检验&#xff08;图形方法、偏度和峰度、统计&#xff08;拟合优度&#xff09;检验&#xff09;_sm.distributions.ecdf-CSDN博客 K-S检验法判断…

一文详解SpringBoot 定时任务(cron表达式)

IDE&#xff1a;IntelliJ IDEA 2022.2.3 x64 操作系统&#xff1a;win10 x64 位 家庭版 JDK: 1.8 文章目录 一、如何开启一个SpringBoot定时任务&#xff1f;二、cron表达式详解2.1 语法格式2.2 符号解析2,2.1 通用符号: , - * /2.2.2 专有符号&#xff1a;&#xff1f;L w # c…

Linux操作系统——进程(四)进程切换与命令行参数

进程切换 概念引入 下面我们先了解几个概念&#xff1a; 竞争性: 系统进程数目众多&#xff0c;而CPU资源只有少量&#xff0c;甚至1个&#xff0c;所以进程之间是具有竞争属性的。为了高效完成任务&#xff0c;更合理竞争相关资源&#xff0c;便具有了优先级 独立性: 多进程…

关于Smartbi登录代码逻辑漏洞的动态情报

一、基本内容 近日&#xff0c;思迈特软件核查发现存在“登录代码逻辑漏洞”问题&#xff0c;重点影响范围涉及Smartbi V9及其以上版本。该漏洞可能导致攻击者利用逻辑缺陷对目标系统进行攻击&#xff0c;造成敏感信息泄露和远程代码执行的风险。 二、相关发声情况 Smartbi是…