C语言与函数栈帧

news2024/11/16 21:52:27

目录

函数栈帧

函数栈帧的前置知识

相关寄存器

相关汇编指令

知识基础

函数栈帧基础剖析

main函数由其他函数调用

函数栈帧分析

反汇编

函数栈帧的创建

函数体

变量a的创建

变量b的创建

变量ret的创建

传参

函数调用

变量z的创建

执行加法

返回计算结果

函数栈帧销毁

总结


函数栈帧

在C语言中,程序是以函数为基本单位,而函数的调用、函数返回值的处理以及函数参数的传递等问题都与函数栈帧有关

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:

  • 函数参数和函数返回值
  • 临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
  • 保存上下文信息(包括在函数调用前后需要保持不变的寄存器)

栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言

在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可

以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出

栈(First In Last Out, FIFO)。就像叠成一叠的书,先叠上去的书在最下面,因此要最后才能取出

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。

在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。在我们常见的i386或者x86-64下,栈顶由成为esp的寄存器进行定位的

函数栈帧的前置知识

相关寄存器

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

📌

寄存器ebpesp

esp寄存器全称为Extended Stack Pointer,中文名为扩展栈指针寄存器。它内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

ebp寄存器全称为Extended Base Pointer,中文名为扩展基址指针寄存器。它内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。这两个寄存器在汇编语言中起到了重要的作用,尤其是在函数调用和参数传递过程中

相关汇编指令

mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变
pop:将数据弹出并放置在对应寄存器中,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,压入返回地址并转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令
lea:加载有效地址

知识基础

  • 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
  • 函数栈帧空间的维护是使用了2个寄存器:espebpebp记录的是栈底的地址,esp记录的是栈顶的地址
  • 内存中,上面是高地址,下面是低地址

📌

本次为了演示和理解方便,将采用上面是低地址,上面是高地址

函数栈帧基础剖析

演示环境:编译器VS2013

演示代码:

#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 ret = 0;
    ret = Add(a, b);
    printf("%d\n", ret);

    return 0;
}

main函数由其他函数调用

main函数在编译器VS2013上由以下函数调用:

在VS2013中,main函数被__tmainCRTStartup()调用,对应main的返回值给一个名为mainret的变量

📌

上述内容了解即可,在不同的编译器下调用可能不同,在下面的分析中不包括调用main函数的函数

函数栈帧分析

反汇编

首先,执行函数的调试,并转到反汇编

int main()
{
00091410  push        ebp  
00091411  mov         ebp,esp  
00091413  sub         esp,0E4h  
00091419  push        ebx  
0009141A  push        esi  
0009141B  push        edi  
0009141C  lea         edi,[ebp-0E4h]  
00091422  mov         ecx,39h  
00091427  mov         eax,0CCCCCCCCh  
0009142C  rep stos    dword ptr es:[edi]  
    int a = 10;
0009142E  mov         dword ptr [a],0Ah  
    int b = 20;
00091435  mov         dword ptr [b],14h  
    int ret = 0;
0009143C  mov         dword ptr [ret],0  
    ret = Add(a, b);
00091443  mov         eax,dword ptr [b]  
00091446  push        eax  
00091447  mov         ecx,dword ptr [a]  
0009144A  push        ecx  
0009144B  call        _Add (0910E1h)  
00091450  add         esp,8  
00091453  mov         dword ptr [ret],eax  
    printf("%d\n", ret);
00091456  mov         esi,esp  
00091458  mov         eax,dword ptr [ret]  
0009145B  push        eax  
0009145C  push        95858h  
00091461  call        dword ptr ds:[99114h]  
00091467  add         esp,8  
0009146A  cmp         esi,esp  
0009146C  call        __RTC_CheckEsp (09113Bh)  
    //system("pause");

    return 0;
00091471  xor         eax,eax  
}

函数栈帧的创建

00091410  push        ebp  
00091411  mov         ebp,esp  
00091413  sub         esp,0E4h  
00091419  push        ebx  
0009141A  push        esi  
0009141B  push        edi  
0009141C  lea         edi,[ebp+FFFFFF1Ch]  
00091422  mov         ecx,39h  
00091427  mov         eax,0CCCCCCCCh  
0009142C  rep stos    dword ptr es:[edi]  

00F81410 push ebp

对于上述汇编指令来说,首先push表示压栈操作,压入ebp中的数据,并使esp指向栈低该位置,如下图所示

当前ebpesp中的值:

00F81411 mov ebp,esp

接下来执行mov指令,该指令表示,将esp中的内容给ebp

对于上述两句汇编代码,VS2013的效果如下:

  • 未执行push指令之前ebpesp中的值:

  • 执行push指令之后ebpesp中的值:

观察到esp中的值被赋值到了ebp

00F81413 sub esp,0E4h

接下来执行sub指令,sub指令代表相减,该指令表示将esp指令中的值减去0E4h(h为16进制后缀),如下图所示

当前ebp和esp中的值:

ebp的地址未改变,esp中的地址改变,即esp中的初始地址减去0E4h后的值

00F81419 push ebx

00F8141A push esi

00F8141B push edi

接下来的三条push指令,向esp指向的空间上方压入数据,并且esp要指向新的位置,如图所示

00F81419 push ebx

当前esp指向的地址空间中存储着ebx中的数值

00F8141A push esi

当前esp指向的地址空间中存储着esi中的数值

00F8141B push edi

当前esp指向的地址空间中存储着edi中的数值

📌

注意每一次push操作相当于esp-4(减去双字节dword

00F8141C lea edi,[ebp-0E4h]

接下来执行lea指令,lea指令表示加载有效地址,上述汇编代码中表示将ebp-0E4h的地址加载到edi中,因为刚才esp中所指的地址为ebp-0E4h,故ebp-0E4h则就是esp未压入edi、esi和ebx之前的地址

当前edi中的地址

对照初始时esp中的地址

当前的espesiebxedi

当前espesiebxedi的地址

00F81422 mov ecx,39h

00F81427 mov eax,0CCCCCCCCh

00F8142C rep stos dword ptr es:[edi]

接下来执行三条指令,代表从edi空间开始,每次增加4(dword),移动57(十六进制的39)行将该空间内容存入0cccccccch,一直到ebp所在地址

00F81422 mov ecx,39h

00F81427 mov eax,0CCCCCCCCh

00F8142C rep stos dword ptr es:[edi]

//当前的esp
0x00EFF8F4  0e 11 09 00
//当前的esi
0x00EFF8F8  0e 11 09 00
//当前的ebx
0x00EFF8FC  00 00 c0 00
//初始的edi
//总共57行,对应ecx中的39h行dword(cccc为word(两个字节类似short类型),cccccccc为dword(四个字节类似int类型),即)
0x00EFF900  cc cc cc cc
0x00EFF904  cc cc cc cc
0x00EFF908  cc cc cc cc
0x00EFF90C  cc cc cc cc
0x00EFF910  cc cc cc cc
0x00EFF914  cc cc cc cc
0x00EFF918  cc cc cc cc
0x00EFF91C  cc cc cc cc
0x00EFF920  cc cc cc cc
0x00EFF924  cc cc cc cc
0x00EFF928  cc cc cc cc
0x00EFF92C  cc cc cc cc
0x00EFF930  cc cc cc cc
0x00EFF934  cc cc cc cc  
0x00EFF938  cc cc cc cc  
0x00EFF93C  cc cc cc cc  
0x00EFF940  cc cc cc cc  
0x00EFF944  cc cc cc cc  
0x00EFF948  cc cc cc cc  
0x00EFF94C  cc cc cc cc  
0x00EFF950  cc cc cc cc  
0x00EFF954  cc cc cc cc  
0x00EFF958  cc cc cc cc  
0x00EFF95C  cc cc cc cc  
0x00EFF960  cc cc cc cc  
0x00EFF964  cc cc cc cc  
0x00EFF968  cc cc cc cc  
0x00EFF96C  cc cc cc cc  
0x00EFF970  cc cc cc cc  
0x00EFF974  cc cc cc cc  
0x00EFF978  cc cc cc cc  
0x00EFF97C  cc cc cc cc  
0x00EFF980  cc cc cc cc  
0x00EFF984  cc cc cc cc  
0x00EFF988  cc cc cc cc  
0x00EFF98C  cc cc cc cc  
0x00EFF990  cc cc cc cc  
0x00EFF994  cc cc cc cc  
0x00EFF998  cc cc cc cc  
0x00EFF99C  cc cc cc cc  
0x00EFF9A0  cc cc cc cc  
0x00EFF9A4  cc cc cc cc  
0x00EFF9A8  cc cc cc cc  
0x00EFF9AC  cc cc cc cc  
0x00EFF9B0  cc cc cc cc  
0x00EFF9B4  cc cc cc cc  
0x00EFF9B8  cc cc cc cc  
0x00EFF9BC  cc cc cc cc  
0x00EFF9C0  cc cc cc cc  
0x00EFF9C4  cc cc cc cc  
0x00EFF9C8  cc cc cc cc  
0x00EFF9CC  cc cc cc cc  
0x00EFF9D0  cc cc cc cc  
0x00EFF9D4  cc cc cc cc  
0x00EFF9D8  cc cc cc cc  
0x00EFF9DC  cc cc cc cc  
0x00EFF9E0  cc cc cc cc  
//当前的ebp
0x00EFF9E4  34 fa ef 00  

对照初始的edi

上述代码相当于下面C语言的代码

edi = ebp-0E4h;
ecx = 0x39;
eax = 0xCCCCCCCC;
for(; ecx = 0; --ecx,edi+=4)
{
    *(int*)edi = eax;
}

至此,main函数的函数栈帧空间创建完成,从esp空间开始一直到ebp为止的空间全为main函数的栈帧空间

函数体

//Add函数
int Add(int x, int y)
{
000913C0  push        ebp  
000913C1  mov         ebp,esp  
000913C3  sub         esp,0CCh  
000913C9  push        ebx  
000913CA  push        esi  
000913CB  push        edi  
000913CC  lea         edi,[ebp-0CCh]  
000913D2  mov         ecx,33h  
000913D7  mov         eax,0CCCCCCCCh  
000913DC  rep stos    dword ptr es:[edi]  
    int z = 0;
000913DE  mov         dword ptr [z],0  
    z = x + y;
000913E5  mov         eax,dword ptr [x]  
000913E8  add         eax,dword ptr [y]  
000913EB  mov         dword ptr [z],eax  
    return z;
000913EE  mov         eax,dword ptr [z]  
}
000913F1  pop         edi  
000913F2  pop         esi  
000913F3  pop         ebx  
000913F4  mov         esp,ebp  
000913F6  pop         ebp  
000913F7  ret  

//main函数
    int a = 10;
0009142E  mov         dword ptr [ebp-8],0Ah  
    int b = 20;
00091435  mov         dword ptr [ebp-14h],14h  
    int ret = 0;
0009143C  mov         dword ptr [ebp-20h],0  
    ret = Add(a, b);
00091443  mov         eax,dword ptr [ebp-14h]  
00091446  push        eax  
00091447  mov         ecx,dword ptr [ebp-8]  
0009144A  push        ecx  
0009144B  call        000910E1  
00091450  add         esp,8  
00091453  mov         dword ptr [ebp-20h],eax  

📌

观察变量的创建时,关闭“显示符号名”

变量a的创建

int a = 10;

0009142E mov dword ptr [ebp-8],0Ah

执行mov指令,将0Ah值放到地址ebp-8

当前ebp中的地址为:

两次减4到新地址,并将该地址上的值从0xcccccccc修改为0x0000000a(注意小端存储)

变量b的创建

int b = 20;

00091435 mov dword ptr [ebp-14h],14h

接下来执行mov指令,将14h值放到ebp-14h(即ebp-20)地址处

当前ebp中的地址为:

5次减4到新地址,并将该地址上的值从0xcccccccc修改为0x00000014(注意小端存储)

变量ret的创建

int ret = 0;

0009143C mov dword ptr [ebp-20h],0

接下来执行mov指令,将数值0放置到ebp-20hebp-32)的地址上

当前ebp中的地址为:

8次减4到新地址,并将该地址上的值从0xcccccccc修改为0x00000000(注意小端存储)

至此所有main函数中的局部变量全部创建完成,如下图所示:

传参

在调用函数之前,需要压栈进行传参操作

00091443  mov         eax,dword ptr [ebp-14h]  
00091446  push        eax  
00091447  mov         ecx,dword ptr [ebp-8]  
0009144A  push        ecx  

00091443 mov eax,dword ptr [ebp-14h]

执行mov指令,将ebp-14h地址上的值给eax,因为ebp-14h为变量b所在的地址,即将b的值给eax

00091446 push eax

接下来执行push指令,将eax值压入栈顶,并使esp指针指向该位置

执行push之后的esp地址:

对照push执行之前的esp的地址:

00091447 mov ecx,dword ptr [ebp-8]

接下来执行mov指令,将ebp-8地址上的数据,即变量a中的值给ecx

0009144A push ecx

接下来执行push指令,将ecx值压入栈顶,并使esp指针指向该位置

执行push之后的esp地址:

对照push执行之前的esp的地址:

函数调用
0009144B  call        000910E1  
00091450  add         esp,8  
00091453  mov         dword ptr [ebp-20h],eax  

首先执行call指令,进行函数调用,在执行call指令之前先会把call指令的下一条指令的地址进行压栈操作,这个操作是为了解决当函数调用结束后要回到call指令的下一条指令的地方,继续往后执行

执行call指令后的esp的地址和值:

对照未执行call指令之前esp的地址:

//Add函数跳转
_Add:
000910E1  jmp         000913C0 
//Add函数体
int Add(int x, int y)
{
000913C0  push        ebp  
000913C1  mov         ebp,esp  
000913C3  sub         esp,0CCh  
000913C9  push        ebx  
000913CA  push        esi  
000913CB  push        edi  
000913CC  lea         edi,[ebp-0CCh]  
000913D2  mov         ecx,33h  
000913D7  mov         eax,0CCCCCCCCh  
000913DC  rep stos    dword ptr es:[edi]  
    int z = 0;
000913DE  mov         dword ptr [z],0  
    z = x + y;
000913E5  mov         eax,dword ptr [x]  
000913E8  add         eax,dword ptr [y]  
000913EB  mov         dword ptr [z],eax  
    return z;
000913EE  mov         eax,dword ptr [z]  
}
000913F1  pop         edi  
000913F2  pop         esi  
000913F3  pop         ebx  
000913F4  mov         esp,ebp  
000913F6  pop         ebp  
000913F7  ret  

000910E1 jmp 000913C0

执行jmp指令,跳转到指定函数位置

进入Add函数后,依旧先要开辟函数栈帧空间

000913C0  push        ebp  
000913C1  mov         ebp,esp  
000913C3  sub         esp,0CCh  
000913C9  push        ebx  
000913CA  push        esi  
000913CB  push        edi  
000913CC  lea         edi,[ebp-0CCh]  
000913D2  mov         ecx,33h  
000913D7  mov         eax,0CCCCCCCCh  
000913DC  rep stos    dword ptr es:[edi]  

000913C0 push ebp

000913C1 mov ebp,esp

移动espebp地址,使其开始维护Add函数的栈帧空间

后面的操作与main函数相同,不再重复介绍

栈帧空间开辟结果图

//当前的esp
0x00EFF80C  e4 f9 ef 00  
//当前的esi
0x00EFF810  0e 11 09 00  
//当前的ebx
0x00EFF814  00 00 c0 00  
//初始位置的edi
//33h(十进制下的51)行初始化为0xcccccccc
0x00EFF818  cc cc cc cc  
0x00EFF81C  cc cc cc cc  
0x00EFF820  cc cc cc cc  
0x00EFF824  cc cc cc cc  
0x00EFF828  cc cc cc cc  
0x00EFF82C  cc cc cc cc  
0x00EFF830  cc cc cc cc  
0x00EFF834  cc cc cc cc  
0x00EFF838  cc cc cc cc  
0x00EFF83C  cc cc cc cc  
0x00EFF840  cc cc cc cc  
0x00EFF844  cc cc cc cc  
0x00EFF848  cc cc cc cc  
0x00EFF84C  cc cc cc cc  
0x00EFF850  cc cc cc cc  
0x00EFF854  cc cc cc cc  
0x00EFF858  cc cc cc cc  
0x00EFF85C  cc cc cc cc  
0x00EFF860  cc cc cc cc  
0x00EFF864  cc cc cc cc  
0x00EFF868  cc cc cc cc  
0x00EFF86C  cc cc cc cc  
0x00EFF870  cc cc cc cc  
0x00EFF874  cc cc cc cc  
0x00EFF878  cc cc cc cc  
0x00EFF87C  cc cc cc cc  
0x00EFF880  cc cc cc cc  
0x00EFF884  cc cc cc cc  
0x00EFF888  cc cc cc cc  
0x00EFF88C  cc cc cc cc  
0x00EFF890  cc cc cc cc  
0x00EFF894  cc cc cc cc  
0x00EFF898  cc cc cc cc  
0x00EFF89C  cc cc cc cc  
0x00EFF8A0  cc cc cc cc  
0x00EFF8A4  cc cc cc cc  
0x00EFF8A8  cc cc cc cc  
0x00EFF8AC  cc cc cc cc  
0x00EFF8B0  cc cc cc cc  
0x00EFF8B4  cc cc cc cc  
0x00EFF8B8  cc cc cc cc  
0x00EFF8BC  cc cc cc cc  
0x00EFF8C0  cc cc cc cc  
0x00EFF8C4  cc cc cc cc  
0x00EFF8C8  cc cc cc cc  
0x00EFF8CC  cc cc cc cc  
0x00EFF8D0  cc cc cc cc  
0x00EFF8D4  cc cc cc cc  
0x00EFF8D8  cc cc cc cc  
0x00EFF8DC  cc cc cc cc  
0x00EFF8E0  cc cc cc cc  
//当前的ebp
0x00EFF8E4  e4 f9 ef 00  
变量z的创建

int z = 0;

000913DE mov dword ptr [ebp-8],0

执行过程与main函数相同,不再介绍,结果如图

执行加法

z = x + y;

000913E5 mov eax,dword ptr [ebp+8]

000913E8 add eax,dword ptr [ebp+0Ch]

000913EB mov dword ptr [ebp-8],eax

接下来执行z = x + y,首先执行mov指令,将ebp+8位置的值放到eax寄存器中,如下图所示

再执行add指令,将ebp+0ch(即ebp+12)位置的值与eax中的值相加放置到eax

执行完add指令后eax当前值为30

最后执行mov指令,将eax中的值移动到ebp-8(即变量z)的地址处

返回计算结果

return z;

000913EE mov eax,dword ptr [ebp-8]

执行mov指令,将ebp-8处的值放到eax

函数栈帧销毁
000913F1  pop         edi  
000913F2  pop         esi  
000913F3  pop         ebx  
000913F4  mov         esp,ebp  
000913F6  pop         ebp  
000913F7  ret  

000913F1 pop edi

000913F2 pop esi

000913F3 pop ebx

执行三次pop指令,依次使ediesiebx出栈,同时使esp指针指向ebp-0CCh位置处

000913F4 mov esp,ebp

接下来执行mov指令,将初始的ebp地址给esp,使函数栈帧空间释放

初始时espebp的地址

执行mov指令后的espebp的地址

000913F6 pop ebp

接下来执行pop指令,使开始pushebp出栈,并且移动ebpmain函数的ebp位置

000913F7 ret

最后执行ret指令,将ebp当前地址0x00091450存入寄存器eip中,并使esp指针向下移动双字节,并且此时Add函数中所有的局部变量将销毁

00091450 add esp,8

接下来执行main函数中call指令的下一条指令,即地址0x00091450对应的指令

执行add指令,将8加至esp中并使esp移动到main函数的栈顶

00091453 mov dword ptr [ebp-20h],eax

执行mov指令,将eax的值给地址ebp-20h(即变量ret

接着main函数继续执行,直到结尾函数栈帧销毁

💡

返回对象是内置类型时,一般都是通过寄存器来带回返回值的,返回对象如果时较大的对象时,一般会在主调函数的栈帧中开辟一块空间,然后把这块空间的地址,隐式传递给被调函数,在被调函数中通过地址找到主调函数中预留的空间,将返回值直接保存到主调函数的

总结

通过简单分析main函数和Add函数之间的栈帧空间开辟以及相互之间的调用,了解到下面几点:

  1. 任何函数在执行正式代码之前都需要进行函数栈帧的空间开辟,而函数栈帧的开辟涉及到esp栈顶指针和ebp栈低指针,这两个指针负责维护二者范围内函数栈帧空间,在此过程中,空间中的内容会被赋值为0xcccccccc,导致未赋初始值的变量为随机值
  2. 函数局部变量的开辟是通过栈低指针进行地址运算为不同的变量开辟空间
  3. 在函数调用过程中,后面的函数参数会被先压栈,再者就是前一个变量,并且形参压栈比调用的函数的栈帧空间开辟的时间要早,由全新的一块空间负责存储调用函数时传入的实参的值,所以在函数中改变形参不会影响实参,因为形参和实参是两块不同的空间。最后在调用的函数的栈帧空间销毁时,先销毁调用的函数的栈帧空间,再通过esp指针移动从而销毁形参
  4. 在调用的函数返回值时,并不是变量将该值带回,而是通过寄存器存储值,将值返回给调用函数的函数接收

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

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

相关文章

HarmonyOS Next开发----k线图滑动问题

前言 最近做股票软件鸿蒙版本的适配&#xff0c;K线趋势图的手势交互上遇到了问题&#xff0c;这里记录下~ 功能需求&#xff1a; 实现k线趋势图滑动及fling的效果 思路&#xff1a; 1. 借鉴Flutter版本的思路&#xff0c;在K线趋势图上面叠加一个Scroll布局&#xff0c;使…

MySQL学习记录——사 表结构的操作

文章目录 1、创建表2、查看表结构3、改变表结构4、删除表5、总结 1、创建表 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 例子 create table users ( id int, name varchar(20) c…

python-产品篇-游戏-玛丽冒险

文章目录 开发环境要求运行方法代码效果 开发环境要求 本系统的软件开发及运行环境具体如下。 &#xff08;1&#xff09;操作系统&#xff1a;Windows 7、Windows 8、Windows 10。 &#xff08;2&#xff09;Python版本&#xff1a;Python 3.7.0。 &#xff08;3&#xff09;…

109 C++ STL 分配器概述,使用,工作原理说明 非重点。

一。分配器allocator概述 分配器 (allocator) 是C STL库的基石之一&#xff0c;它是一种策略模式&#xff0c;允许用户将内存管理从容器中解耦出来&#xff0c;进行更具体化的操作。通过使用 allocator&#xff0c;我们可以自定义内存的分配和释放方式&#xff0c;从而可以更好…

Springboot 整合 Elasticsearch(二):使用HTTP请求来操作ES

&#x1f4c1;前情提要&#xff1a;Springboot整合Elasticsearch&#xff08;一&#xff09;&#xff1a;Linux下安装 Elasticsearch 8.x 上回说到&#xff0c;Elasticsearch 已经安装完成&#xff0c;不过我们使用7.17.5 版本继续后文&#xff1a; 一、使用 elasticsearch-hea…

【网站项目】037物流管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Matlab:利用1D-CNN(一维卷积神经网络),分析高光谱曲线数据或时序数据

1DCNN 简介&#xff1a; 1D-CNN&#xff08;一维卷积神经网络&#xff09;是一种特殊类型的卷积神经网络&#xff0c;设计用于处理一维序列数据。这种网络结构通常由多个卷积层和池化层交替组成&#xff0c;最后使用全连接层将提取的特征映射到输出。 以下是1D-CNN的主要组成…

Mac最实用的日常快捷键,最方便快捷的Mac使用技巧合集

今天小编给大家分享一下这几年来使用Mac过程中的各种小技巧。&#xff0c;大家不用担心&#xff0c;下面的各种小技巧在apple其他各型号电脑中几乎也是都是通用的&#xff0c;话不多说&#xff0c;下面开始&#xff01; 屏幕相关 &#xff08;1&#xff09;截屏 ctrlshift3 截…

jsp商场会员卡管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 商场会员卡管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.…

计组学习笔记2024/2/5

1. 2. 3. 1.这么多步,才完成第一条指令,通过0索引来找到2 2.PC的值是对应着MAR来的,为了更好地找到地址 3.操作码, 地址码这些东西都是放在存储体里面的 MAR和MDR只是一个中转站 MAR对应着拿到各个部件给出的主存地址 MDR对应着拿到各个部件给出的指令 4.取指令完成后就自…

2024Node.js零基础教程(小白友好型),nodejs新手到高手,(五)NodeJS入门——http模块

044_http模块_创建HTTP服务端 hello&#xff0c;大家好&#xff0c;那这个小节我们来使用 nodejs 创建一个 http 的服务&#xff0c;有了这个 http 服务之后&#xff0c;我们就可以处理浏览器所发送过来的请求&#xff0c;并且还可以给这个浏览器返回响应。 顺便说一下&#x…

数据分析基础之《pandas(5)—文件读取与存储》

一、概述 1、我们的数据大部分存在于文件当中&#xff0c;所以pandas会支持复杂的IO操作&#xff0c;pandas的API支持众多文件格式&#xff0c;如CSV、SQL、XLS、JSON、HDF5 二、CSV 1、读取csv文件 read_csv(filepath_or_buffer, sep,, delimiterNone) 说明&#xff1a; fi…

<.Net>使用visual Studio 2022在VB.net中新添自定义画图函数(优化版)

前言 这是基于我之前的一篇博文&#xff1a; 使用visual Studio 2019在VB.net中新添自定义画图函数 在此基础上&#xff0c;我优化了一下&#xff0c;改进了UI&#xff0c;添加了示例功能&#xff0c;即以画圆函数为基础&#xff0c;添加了走马灯功能。 先看一下最终效果&#…

在线JSON解析格式化工具

在线JSON解析格式化工具 - BTool在线工具软件&#xff0c;为开发者提供方便。JSON在线可视化工具:提供JSON视图,JSON格式化视图,JSON可视化,JSON美化,JSON美化视图,JSON在线美化,JSON结构化,JSON格式化,JSON中文Unicode等等。以清晰美观的结构化视图来展示json,可伸缩折叠展示,…

【Linux】Linux权限(下)

Hello everybody!在上一篇文章中&#xff0c;权限讲了大部分内容。今天继续介绍权限剩下的内容&#xff0c;希望大家看过这篇文章后都能有所收获&#xff01; 1.更改文件的拥有者和所属组 对于普通用户&#xff0c;文件的拥有者和所属组都无权修改。 、 、 但root可以修改文件…

071:vue中过滤器filters的使用方法(图文示例)

第071个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使用&#xff0c;computed&a…

双侧条形图绘制教程

写在前面 双侧条形图在我们的文章中也是比较常见的&#xff0c;那么这样的图形是如何绘制的呢&#xff1f; 以及它使用的数据类型是什么呢&#xff1f; 这些都是我们在绘制图形前需要掌握的&#xff0c;至少我们知道绘图的数据集如何准备&#xff0c;这样才踏出第一步。 今天…

Unity接入GVoice腾讯实时语音

Unity接入GVoice腾讯实时语音 一、介绍二、注册GVoice创建项目语音服务1.创建项目2.申请语音权限3.项目管理查看SDK初始化的一些参数和基本信息4.GVoice检测 三、SDK下载SDK是分为两种类型&#xff1a;独立版集成板 SDK放入Unity工程中 四、语音代码写法五、GVoice踩坑语音权限…

知到如何找答案?这7款足够解决问题 #笔记#其他

在这个信息爆炸的时代&#xff0c;合理利用学习工具可以帮助我们过滤和获取有用的知识。 1.网易公开课 这是一个可以帮你找到国内外演讲课程的学习APP&#xff0c;提供了多个专业的视频课程&#xff0c;而且还有丰富的TED、精品国外英语纪录片等。 其中涵盖的大学专业课程包…

ClickHouse基于数据分析常用函数

文章标题 一、WITH语法-定义变量1.1 定义变量1.2 调用函数1.3 子查询 二、GROUP BY子句&#xff08;结合WITH ROLLUP、CUBE、TOTALS&#xff09;三、FORM语法3.1表函数3.1.1 file3.1.2 numbers3.1.3 mysql3.1.4 hdfs 四、ARRAY JOIN语法&#xff08;区别于arrayJoin(arr)函数&a…