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

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

文章目录

    • 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们觉得博主讲的不错的话,请动动你们滴滴给博主点个赞,你们滴鼓励将成为博主源源不断滴动力!

相关文章:

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

文章目录 1:什么是函数栈帧2:理解函数栈帧能解决什么问题呢3:函数栈帧的创建与销毁的解析3.1:什么是栈3.2:认识相关寄存器与汇编指令相关寄存器相关汇编指令 3.3 解析函数栈帧的创建和销毁3.3.1 预备知识3.3.2 详细解析一:调用main函数,为main函数开辟函数栈帧First:push前push…...

【深度学习-目标检测】03 - Faster R-CNN 论文学习与总结

论文地址&#xff1a;Faster R-CNN: Towards Real-Time ObjectDetection with Region Proposal Networks 论文学习 1. 摘要与引言 研究背景与挑战&#xff1a;当前最先进的目标检测网络依赖于 区域提议&#xff08;Region Proposals&#xff09;来假设目标的位置&#xff0c…...

oracle11体系结构二-存储结构

数据区&#xff1a; 数据区&#xff08;数据扩展区&#xff09;由一组连续的oracle数据块所构成的存储结构&#xff0c;一个或多个数据块组成一个数据区&#xff0c;一个或多个数据区组成一个段。当段中所有空间被使用完后&#xff0c;oracle系统将自动为该段分配一个新的数据…...

如何通过内网穿透实现远程访问本地Linux SVN服务

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…...

网页乱码问题(edge浏览器)

网页乱码问题&#xff08;edge&#xff09; 文章目录 网页乱码问题&#xff08;edge&#xff09;前言一、网页乱码问题1.是什么&#xff1a;&#xff08;描述&#xff09;2.解决方法&#xff1a;&#xff08;针对edge浏览器&#xff09;&#xff08;1&#xff09;下载charset插…...

泛微OA xmlrpcServlet接口任意文件读取漏洞(CNVD-2022-43245)

CNVD-2022-43245 泛微e-cology XmlRpcServlet接口处存在任意文件读取漏洞&#xff0c;攻击者可利用漏洞获取敏感信息。 1.漏洞级别 中危 2.影响范围 e-office < 9.5 202201133.漏洞搜索 fofa 搜索 app"泛微-OA&#xff08;e-cology&#xff09;"4.漏洞复现 …...

MATLAB ga函数的使用方法

一、ga句法结构 x ga(fitnessfcn,nvars) x ga(fitnessfcn,nvars,A,b) x ga(fitnessfcn,nvars,A,b,Aeq,beq) x ga(fitnessfcn,nvars,A,b,Aeq,beg,IB,UB) x ga(fitnessfcn,nvars,A,b,Aeq,beq,LB,UB,nonlcon) x ga(fitnessfcn,nvars,A,b,Aeq,beq,LB,UB,nonlcon,options) x …...

基于STM32和MQ-2传感器的无线烟雾检测系统设计

随着科技的不断发展&#xff0c;人们对生活安全的要求也越来越高。其中&#xff0c;烟雾检测系统在预防火灾方面起着至关重要的作用。本文将介绍一种基于STM32和MQ-2传感器的无线烟雾检测系统设计&#xff0c;旨在实时检测环境中的烟雾&#xff0c;并及时发出警报&#xff0c;以…...

华为vrrp+mstp+ospf+dhcp+dhcp relay配置案例

1、左边是vlan 10主桥&#xff0c;右边是vlan 20的主桥&#xff0c;并且互为备桥 2、 vlan 10 vrrp网关默认用左边&#xff0c;vlan 20的vrrp 网关默认用右边&#xff0c;对应mstp生成树 3、两边都track检测&#xff0c;不通就把vrrp减掉60&#xff0c;这样就会自动切另一边了 …...

5-Docker实例-tomcat application

1.安装如下树形结构创建目录及文件,内容如下: 目录结构: [root@centos79 ~]# tree demo demo ├── index.html └── WEB-INF└── web.xml1 directory, 2 files [root@centos79 ~]# index.html文件内容 [root@centos79 demo]# cat index.html <h1>hello dock…...

Pikachu靶场 “Http Header”SQL注入

1. 先在 pikachu 打开 Http Header 注入模块&#xff0c;点击提示 查看登录 账号 和 密码&#xff0c;登陆后去 Burp 中找到登陆的 GET请求 2. 设置payload1 &#xff1a;在 User-Agent最后 输入 查看 数据库名 or updatexml(1,concat(0x7e,database()),0) or 查看 用户名…...

OpenEuler安装内网穿透工具实现ssh连接openEuler系统

文章目录 1. 本地SSH连接测试2. openEuler安装Cpolar3. 配置 SSH公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 本文主要介绍在openEuler中安装Cpolar内网穿透工具实现远程也可以ssh 连接openEuler系统使用. 欧拉操作系统(openEuler, 简称“欧拉”…...

【效率工具】利用python进行本地知识库(PDF和WORK文件内容)的批量模糊搜索

目录 前言 一、为什么要进行本地文档的批量搜索? 二、如何去做呢?...

快速入门学习定时任务框架-xxljob

定时任务框架-xxljob 简介 主要用于分布式任务调度&#xff0c;可以将任务调度和执行分布在多个节点上。它提供了一个集中式的管理平台&#xff0c;支持动态添加、修改、删除任务&#xff0c;以及任务的分片执行&#xff0c;确保任务在分布式环境中的高可用性的一个框架 spr…...

Floyd(弗洛伊德)算法总结

知识概览 Floyd算法适合解决多源汇最短路问题&#xff0c;其中源点是起点&#xff0c;汇点是终点。时间复杂度是。 例题展示 题目链接 活动 - AcWing 系统讲解常用算法与数据结构&#xff0c;给出相应代码模板&#xff0c;并会布置、讲解相应的基础算法题目。https://www.acw…...

西南科技大学计算机网络实验二 (IP协议分析与以太网协议分析)

一、实验目的 通过分析由跟踪执行traceroute程序发送和接收捕获得到的IP 数据报,深入研究在IP 数据报中的各种字段,理解IP协议。基于ARP命令和Ethereal进行以太网帧捕获与分析,理解和熟悉ARP协议原理以及以太网帧格式。 二、实验环境 与因特网连接的计算机网络系统;主机操…...

SICP : The Elements of Programming

好的计算机编程语言应具备的三个特性 基础单元表达式&#xff0c;计算机编程语言最最最基础单元&#xff0c;理应具备的表达式组合的能力&#xff0c;能够通过基础单元表达式组合成更复杂的元素抽象的能力&#xff0c;能通过复杂的元素抽象成更高层的单元 基础单元表达式 加 …...

支付宝、学习强国小程序input、textarea数据双向绑定

前言 和 vue 的绑定有些区别&#xff0c;需要注意。直接 value"{{inputValue}}" 是无法双向绑定的。 正确思路 文档说的比较详细&#xff0c;不过没有组合使用的案例&#xff0c;需要自行理解。这里正确的方法是先用 value 绑定数据&#xff0c;再使用 onInput 事件…...

AI“百模大战”现状:向垂直、B端谋场景,算力仍是主要制约因素

文章目录 每日一句正能量前言AI&#xff08;人工智能&#xff09;大模型正“飞入”百姓家和行业中。向垂直、B端谋场景算力仍是主要制约因素构建“数据-模型-应用”飞轮后记 每日一句正能量 我们必须在失败中寻找胜利&#xff0c;在绝望中寻求希望。 前言 在当前快速发展的人工…...

手机上的软件怎么修改网络IP地址

在手机上修改网络IP地址通常需要通过以下两种方法&#xff1a; 1. 使用VPN&#xff08;虚拟私人网络&#xff09;或代理软件&#xff1a; 步骤如下&#xff1a; - 下载并安装一个可靠的VPN或代理软件到你的手机上。 - 打开VPN或代理软件&#xff0c;选择一个你希望获取IP地址…...

返回按钮点击坐标

返回按钮的点击坐标&#xff08;按钮本身的相对位置&#xff09;主要用于自绘控件时响应点击对应的数据变化。效果如下图&#xff1a; 代码实现 private void button1_MouseClick(object sender, MouseEventArgs e){Point p e.Location;this.Text p.ToString();} 利用 Mouse…...

arm32 arm64 读取PMCCNTR cpu cycle counter

ARM 的时钟周期计数保存在PMCCNTR 寄存器&#xff0c;不像x86用户态可以直接读取&#xff0c;需内核态使能&#xff0c;一种是在内核中使能&#xff0c;比如init&#xff0c;比较简单的是在模块中使能。 本来写了两个&#xff0c;arm32一个&#xff0c;arm64一个&#xff0c;方…...

vue 项目/备案网页/ip网页打包成 apk 安装到平板/手机(含vue项目跨域代理打包成apk后无法访问接口的解决方案)

下载安装HBuilder X编辑器 https://www.dcloud.io/hbuilderx.html 新建 5APP 项目 打开 HBuilder X&#xff0c;新建项目 此处项目名以 ‘test’ 为例 含跨域代理的vue项目改造 若 vue 项目中含跨域代理&#xff0c;如 vue.config.js module.exports {publicPath: "./&…...

面试复盘4——后端开发——一面

前言 本文主要用于个人复盘学习&#xff0c;因此为保障公平&#xff0c;所以本文不指出公司名&#xff0c;题目编号只是为了自己区别而已。对待面经&#xff0c;望读者还是更多从其中学习总结&#xff0c;而不是去碰原题。 面试岗位信息 北京某初创&#xff0c;go开发&#…...

使用 Postman 进行并发请求:实用教程与最佳实践

背景介绍 最近&#xff0c;我们发起了一个在线图书管理系统的项目。我负责的一个关键模块包括三个主要后台接口&#xff1a; 实现对books数据的检索。实施对likes数据的获取。通过collections端点访问数据。 应对高流量的挑战 在设计并部署接口时&#xff0c;我们不可避免地…...

河南工程学院第六届程序设计竞赛-A组-题解

更好的阅读体验 \color{red}{更好的阅读体验} 更好的阅读体验 远古时期的签到题 原题链接 描述&#xff1a; 远古时期奇妙的事情… 远古时期有一个比赛&#xff0c;里面有这样一道签到题&#xff1a; 给定一个正整数 N N N求这个整数转化为二进制后的数有多少位是 0 0 0。…...

韩版传奇 2 源码分析与 Unity 重制(二)客户端启动与交互流程

专题介绍 该专题将会分析 LOMCN 基于韩版传奇 2&#xff0c;使用 .NET 重写的传奇源码&#xff08;服务端 客户端&#xff09;&#xff0c;分析数据交互、状态管理和客户端渲染等技术&#xff0c;此外笔者还会分享将客户端部分移植到 Unity 和服务端用现代编程语言重写的全过…...

JVM面试——运行时数据区

一&#xff1a;JVM的运行时内存区域是怎样的? 根据Java虚拟机规范的定义&#xff0c;JVM的运行时内存区域主要由程序计数器、虚拟机栈、本地方法 栈、Java堆、方法区和以及运行时常量池组成。其中堆、方法区以及运行时常量池是线程之间共享的区域&#xff0c;而栈&#xff08…...

ssh工具 向指定的ssh服务器配置公钥

此文分享一个python脚本,用于向指定的ssh服务器配置公钥,以达到免密登录ssh服务器的目的。 效果演示 🔥完整演示效果 👇第一步,显然,我们需要选择功能 👇第二步,确认 or 选择ssh服务器 👇第三步,输入ssh登录密码,以完成公钥配置 👇验证,我们通过ssh登录…...

uni-app pages.json之globalStyle全局页面样式配置

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…...

Blazor 混合开发_MAUI+Vue_WPF+Vue

Blazor 混合开发_MAUIVue_WPFVue 背景混合开发的核心为什么必须使用 wwwroot 文件夹放置 Web 项目文件 创建 MAUI 项目创建 wwwroot 文件夹服务注册创建 _import.razor添加 Main.razor 组件修改 MainPage.xaml 文件 创建 WPF 项目创建 wwwroot 文件夹服务注册创建 _import.razo…...

udp异步方式接收消息

C#实现 //定义结构体 public struct UdpState { public UdpClient u; public IPEndPoint e; } private UdpClient _client; //_client的初始化请参考其他资料 IPEndPoint remoteEP null; //TODO //public static bool mess…...

【RocketMQ笔记01】安装RocketMQ消息队列运行环境

这篇文章&#xff0c;主要介绍如何安装RocketMQ消息队列运行环境。 目录 一、RocketMQ消息队列 1.1、下载RocketMQ 1.2、解压安装包 1.3、配置RocketMQ环境变量 1.4、修改启动脚本 1.5、启动RocketMQ &#xff08;1&#xff09;启动NameServer &#xff08;2&#xff0…...

使用 Privoxy 实现对多域名的定向转发

需求与思路 内网一台主机想要访问公网的两个不同站点, 想要实现访问两个站点时表现出不同的公网 IP 地址. 即在公网的站点服务器端看到的客户端 IP 是不同的. 思路是搭建两台具有不同公网 IP 的服务器, 分别安装配置 Privoxy 后进行串联, 并将其中一台作为主服务器暴露给内网…...

《PySpark大数据分析实战》-19.NumPy介绍ndarray介绍

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…...

图解LRU缓存

图解LRU缓存 OJ链接 介绍 LRU 缓存机制可以通过哈希表辅以双向链表实现&#xff0c;我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。 双向链表按照被使用的顺序存储了这些键值对&#xff0c;靠近尾部的键值对是最近使用的&#xff0c;而靠近头部的键值对是最久未…...

FFmpeg常见命令行

1、ffmpeg命令行 视频生成图片 ffmpeg -i test.mp4 -r 25 -f image2 data/image%3d.jpg这个命令行使用FFmpeg工具将视频文件&#xff08;test.mp4&#xff09;转换为一系列图像文件。 让我们逐个解释每个参数的含义&#xff1a; -i test.mp4: 指定输入文件为test.mp4。-i是F…...

智能优化算法应用:基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.斑马算法4.实验参数设定5.算法结果6.参考文献7.MA…...

《C++避坑神器·二十五》简单搞懂json文件的读写之遍历json文件读写

json.hpp库放在文章末尾 1、遍历json文件读写 &#xff08;1&#xff09;插入新键值对到json之情形1 原来json文件如下所示&#xff1a; {"Connection": {"IpAddress": "192.168.20.1","Rock": 0,"Solt": 1}, "Data…...

使用 fixture 机制重构 appium_helloworld

一、前置说明 在 pytest 基础讲解 章节,介绍了 pytest 的特性和基本用法,现在我们可以使用 pytest 的一些机制,来重构 appium_helloworld 。 appium_helloworld 链接: 编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来 代码目录结构: pytest.ini 设置: [pyt…...

基于python的excel检查和读写软件

软件版本&#xff1a;python3.6 窗口和界面gui代码&#xff1a; class mygui:def _init_(self):passdef run(self):root Tkinter.Tk()root.title(ExcelRun)max_w, max_h root.maxsize()root.geometry(f500x500{int((max_w - 500) / 2)}{int((max_h - 300) / 2)}) # 居中显示…...

Podman配置mongodb

文章目录 查询镜像拉取镜像查看镜像运行容器创建root用户 查询镜像 podman search mongo拉取镜像 podman pull docker.io/library/mongo查看镜像 podman images运行容器 podman run -d -p 27017:27017 --namemongodb-test docker.io/library/mongo创建root用户 podman exe…...

java实现矩阵谱峰搜索算法

矩阵谱峰搜索算法&#xff0c;也称为矩阵谱峰查找算法&#xff0c;是一种用于搜索二维矩阵中谱峰的方法。谱峰是指在矩阵中的一个元素&#xff0c;它比其上下左右四个相邻元素都大或相等。 该算法的基本思想是从矩阵的中间列开始&#xff0c;找到该列中的最大元素&#xff0c;…...

Jenkins的特殊操作定时自动执行任务以及测试报告调优

java -Dhudson.model.DirectoryBrowserSupport.CSP -jar Jenkins.war 测试报告 不美丽 执行上面的代码 重启jenkins 就好了...

【Grafana】Grafana匿名访问以及与LDAP连接

上一篇文章利用Docker快速部署了Grafana用来展示Zabbix得监控数据&#xff0c;但还需要给用户去创建账号允许他们登录后才能看展示得数据&#xff0c;那有什么办法让非管理员更方便得去访问Grafana呢&#xff1f;下面介绍两个比较方便实现的&#xff1a; 在开始设置前&#xff…...

elasticsearch-py 8.x的一些优势

​ 早在 2022 年 2 月,当 Elasticsearch 8.0 发布时,Python 客户端也发布了 8.0 版本。它是对 7.x 客户端的部分重写,并带有许多不错的功能(如下所述),但也带有弃用警告和重大更改。今天,客户端的 7.17 版本仍然相对流行,每月下载量超过 100 万次,占 8.x 下载量的 ~50…...

RK3588平台开发系列讲解(AI 篇)RKNN 数据结构详解

文章目录 一、rknn_sdk_version二、rknn_input_output_num三、rknn_tensor_attr四、rknn_perf_detail五、rknn_perf_run六、rknn_mem_size七、rknn_tensor_mem八、rknn_input九、rknn_output沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解 RKNN 相关的数…...

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

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

C预处理 | pragma详解

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…...

轻松搭建知识付费小程序:让知识传播更便捷

明理信息科技saas知识付费平台 在当今数字化时代&#xff0c;知识付费已经成为一种趋势&#xff0c;越来越多的人愿意为有价值的知识付费。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。同时&#xff0c;开发和维护…...