函数栈帧的创建和销毁【汇编语言理解】

news2024/10/6 16:30:23

🌹作者:云小逸
📝个人主页:云小逸的主页
📝Github:云小逸的Github
🤟motto:要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。==希望春天来之前,我们一起面朝大海,春暖花开!==🤟
👏专栏:C++👏 👏专栏:Java语言👏👏专栏:Linux学习👏
👏专栏:C语言初阶👏👏专栏:数据结构👏👏专栏:备战蓝桥杯👏

文章目录

  • 前言
  • 1. 什么是函数栈帧
  • 2. 理解函数栈帧能解决什么问题呢?
  • 3. 函数栈帧的创建和销毁解析
    • 3.1 什么是栈?
    • 3.2 认识相关寄存器和汇编指令
      • 3.3 解析函数栈帧的创建和销毁
        • 3.3.1 预备知识
        • 3.3.2 函数的调用堆栈
          • 演示代码:
          • 深度剖析:
    • 总结
      • 1.局部变量是怎么创建的?
      • 2.为什么局部变量的值是随机值?
      • 3.函数是怎么传参的?传参的顺序是怎样的?
      • 4.形参和实参是什么关系?
      • 5.函数调用是怎么做的?
      • 6. 函数调用是结束后怎么返回的?
  • 最后


前言

在前面学习学习C++引用的时候,有不少同学私信我,说对于函数栈帧那一块不是很理解,那么今天我们就来系统地学习一下函数栈帧的创建和销毁,码字不易,希望多多支持!!!

00117AF9.jpg

1. 什么是函数栈帧

在编写 C 语言代码时,我们通常会将一个独立的功能抽象为函数,因此 C 程序是以函数为基本单位的。
那么函数是如何调用的?函数的返回值又是如何返回的?函数参数是如何传递的?这些问题都与函数栈帧有关。


函数栈帧(stack frame)是指在程序的调用栈(call stack)中,函数调用过程中开辟的空间,用于存放以下内容:

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

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

理解函数栈帧可以解决以下问题:

  • 局部变量是如何创建的?
  • 为什么局部变量的内容未初始化时是随机的?
  • 函数调用时参数是如何传递的?传参的顺序是怎样的?
  • 函数的形参和实参分别是如何实例化的?
  • 函数的返回值是如何返回的?

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

3.1 什么是栈?

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

  • 在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)。就像叠成一叠的书,先叠上去的书在最下面,因此要最后才能取出。

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

  • 在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。

  • 在我们常见的 i386 或者 x86-64 下,栈顶由成为 esp 的寄存器进行定位。

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记录的是栈顶的地址。

image.png

  1. 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异,本次演示以VS2019为例。

3.3.2 函数的调用堆栈

演示代码:
#include <stdio.h>
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 3;
	int b = 5;
	int ret = 0;
	ret = Add(a, b);
	printf("%d\n", ret);
	return 0;
}

image.png

在我们平时写的代码中一般都是在main函数调用其他函数,但是我们是否知道其实main函数也是被调用的!!!
我们可以通过vs中的调试来看到这个过程

CT-20230423073404.png

CT-20230423073453.png

image.png
调用顺序是下面的调用上面的

Snipaste_2023-04-23_15-38-27.png

深度剖析:

从上面的知识点,我们可以知道:是通过__tmainCRTStartup函数来调用main函数,因此先建立__tmainCRTStartup函数的栈帧:

1.png
在这里设定下面是高地址,下面是低地址,从高地址想低地址入栈。
好的,我们接下来来看汇编指令:

2.png


int main()

{


  • 007C1940 push ebp
    这一步是将ebp入栈,然后将esp向低地址移动一位(4个字节)到指向push进来的ebp,
    如下图所示:
    3.png

4.png


  • 007C1941 mov ebp,esp
    mov是将后者的值给前者,即将esp赋值给ebp

5.png


  • 007C1943 sub esp,0E4h
    是指将esp减去0E4h(十进制为228),即esp指向的地址变小,指向了上面的某块区域了:

6.png
此时ebp和esp之间存在一块空间就是为main函数预开辟的空间


  • push ebx 将 ebx 的值压入堆栈,堆栈指针 esp 减少 4 个字节。
  • push esi 将 esi 的值压入堆栈,堆栈指针 esp 再减少 4 个字节。
  • push edi 将 edi 的值压入堆栈,堆栈指针 esp 再减少 4 个字节。 这样,ebx、esi 和 edi 的值就被保存在堆栈中,可以在函数执行过程中随时使用,不会被修改。在函数返回时,再通过 pop 指令将这三个寄存器的值恢复到原来的状态。
    7.png

  • lea edi, [ebp-24h] 将 [ebp-24h] 的地址加载到 edi 中,此时 edi 指向分配的内存空间(main函数)的起始地址。
  • mov ecx, 9 将 ecx 设置为 9,表示需要将 9 个 dword(4 字节)内存单元设置为 0xCCCCCCCC。
  • mov eax, 0CCCCCCCCh 将 eax 设置为 0xCCCCCCCC,即将要填充的值。
  • rep stos dword ptr es:[edi] 重复执行将 eax 中的值写入 edi 指向的内存位置,直到 ecx 个内存单元都被填充为止。这样就完成了堆栈内存空间的初始化。

8.png
这个也是为什么如果你不定义一个变量,不初始化,打印的时候会是随机值!!!

到这里为mian函数栈帧的开辟准备工作才完成


  • mov ecx,0AEC003h 将值 0AEC003h 存储到 ECX 中,即将函数 Add 的地址作为参数传递给下一条指令。
  • call 00AE131B 调用函数 Add。此指令将当前指令的下一条指令的地址(即返回地址)压入堆栈,并跳转到函数 Add 的入口地址开始执行函数体。在函数执行完后,再通过 ret 指令返回到调用该函数的指令处继续执行。
    为调用add函数做准备

10: 	int a = 3;
  • mov dword ptr [ebp-8], 3 将值 3 存储到 [ebp-8] 中,即将变量 a 的值设置为 3。

9.png
没有赋值默认就是0CCCCCCCCh(随机值,烫烫烫那种)


11: 	int b = 5;
  • mov dword ptr [ebp-14h], 5 将值 5 存储到 [ebp-14h] 中,即将变量 b 的值设置为 5。

10.png
与a隔8个字节


12: 	int ret = 0;
  • mov dword ptr [ebp-20h],0 将值 0 存储到 [ebp-20h] 中,即将变量 ret 的值初始化为 0。

13: 	ret = Add(a, b);
  • mov eax,dword ptr [ebp-14h] 将 [ebp-14h] 中的值(即变量 b 的值)存储到 EAX 中。
  • push eax 将 EAX 中的值压入堆栈,作为第二个参数传递给函数 Add。

image.png

  • mov ecx,dword ptr [ebp-8] 将 [ebp-8] 中的值(即变量 a 的值)存储到 ECX 中。
  • push ecx 将 ECX 中的值压入堆栈,作为第一个参数传递给函数 Add。

image.png

  • call 00AE11BD call 指令会将当前代码的下一条指令的地址(即 call 指令的下一条指令)压入堆栈,并将程序的执行控制权转移到函数的入口地址。函数执行完毕后,会返回到 call 指令的下一条指令继续执行。在函数调用过程中,参数会被压入堆栈中,函数执行完毕后返回值也会被存储在指定的寄存器中,通常是 EAX 寄存器。因此,在执行 call 00AE11BD 指令之前,需要确保函数的入口地址是有效的,并且函数参数和返回值的处理符合函数定义的要求。

    call指令在执行的过程中,还会把call指令的下一个指令压到栈中:

11.png


这里会执行jmp指令,进行跳转:

  • 00AE131B jmp 00AE19E0 是一条汇编指令,它的作用是将程序的执行控制权无条件地转移到内存地址 00AE19E0 所在的代码处,从而实现代码的跳转。具体来说,该指令会直接将程序的执行控制权转移到指定的地址,不会对跳转前的指令进行任何处理,也不会保留跳转前的任何状态。因此,在执行该指令之前,需要确保跳转的目标地址是有效的,否则可能会导致程序出错或崩溃。

下面这几步和main函数建立的类似:

  1. push ebp:把当前函数的基址指针(EBP)压入堆栈中,为后续的指令执行做准备。
  2. mov ebp, esp:将堆栈指针(ESP)的值赋给基址指针(EBP),这样就可以在函数内部通过基址指针来访问函数参数和局部变量了。
  3. sub esp, 0CCh:为函数的局部变量分配空间,这里是分配了 204(即 0xCC)字节的空间。 这些指令一般出现在函数的开头部分,用于初始化函数的堆栈和局部变量。第三条指令中的 0xCC 可能是根据具体的函数需要来确定的,一般是根据函数内部需要使用的局部变量的大小来决定的。需要注意的是,在函数执行完毕后,需要通过恢复堆栈指针(通过指令 mov esp, ebppop ebp)来释放堆栈空间,以免出现内存泄漏等问题。
    12.png

  1. push ebx:将 EBX 寄存器的值压入堆栈中,为后续的指令执行做准备。
  2. push esi:将 ESI 寄存器的值压入堆栈中,为后续的指令执行做准备。
  3. push edi:将 EDI 寄存器的值压入堆栈中,为后续的指令执行做准备。
  4. lea edi,[ebp-0Ch]:使用 LEA 指令计算出一个地址,该地址是基址指针减去 12(即 [ebp-0Ch]),并将其存储在 EDI 寄存器中。
  5. mov ecx,3:将计数器 ECX 的值设置为 3,以便用于重复执行指令。
  6. mov eax,0CCCCCCCCh:将一个特殊的值(0xCCCCCCCC)存储在 EAX 寄存器中。
  7. rep stos dword ptr es:[edi]:重复执行 STOS 指令,将 EAX 的值(即 0xCCCCCCCC)存储到 EDI 指向的内存地址中,直到计数器 ECX 的值变为 0。 这些指令一般出现在函数的开头部分,用于初始化函数的堆栈和局部变量。其中,第 4 条指令计算出的地址一般用于存储函数中一些需要初始化的变量。第 6 条指令将一个特殊的值存储在 EAX 寄存器中,该值通常用于调试目的,在程序运行时可以检测到是否访问了未初始化的内存地址。第 7 条指令则是重复执行 STOS 指令,将特定的值存储到指定的内存地址中,以便初始化变量。需要注意的是,在函数执行完毕后,需要通过恢复堆栈指针和寄存器的值(通过指令 pop edipop esipop ebxmov esp, ebppop ebp)来释放堆栈空间和还原寄存器的值,以免出现内存泄漏等问题。

13.png


 4: 	int z = 0;
  • 00AE1795 mov dword ptr [ebp-8],0:将常数值 0 存储到 EBP 减去 8 的地址处,即变量 z 的内存地址,以初始化 z 的值为 0。
    5: z = x + y;

  • 00AE179C mov eax,dword ptr [ebp+8] 从 EBP 加上 8 的地址处取出 4 字节的值,即变量 x 的值,并存储到 EAX 寄存器中。

  • 00AE179F add eax,dword ptr [ebp+0Ch] 将从 EBP 加上 12 的地址处取出的 4 字节值(即变量 y 的值)加到 EAX 寄存器中,得到结果并存储到 EAX 寄存器中。

  • 00AE17A2 mov dword ptr [ebp-8],eax 将 EAX 寄存器中的值(即 x + y 的结果)存储到 EBP 减去 8 的地址处,即变量 z 的内存地址中,完成变量 z 的赋值。

    6: return z;

  • 00AE17A5 mov eax,dword ptr [ebp-8] 从 EBP 减去 8 的地址处取出 4 字节的值,即变量 z 的值,并存储到 EAX 寄存器中。

    7: }

这段代码的作用是计算变量 x 和 y 的和,并将结果存储到变量 z 中,最后返回变量 z 的值。需要注意的是,变量 x、y、z 分别存储在 EBP 加上 8、12、8 的地址处,这是因为在函数调用时,参数和局部变量的值都存储在堆栈中,并通过 EBP 寄存器来访问。

image.png


00AE17A5 mov eax,dword ptr [ebp-8]
因为z是局部变量,出了add函数就会被销毁,因此将其赋值为一个全局变量(就是之前我在C++引用那篇文章中提到的【临时变量】)

解释:

这条汇编指令的作用是从 EBP 减去 8 的地址处取出 4 字节的值(即变量 z 的值),并将其存储到 EAX 寄存器中。这是因为在该函数的最后一行代码中,函数需要返回变量 z 的值,而该变量的值已经存储在 EBP 减去 8 的地址处,因此需要将其取出并存储到 EAX 寄存器中,以便作为函数的返回值。需要注意的是,这里的 dword ptr 是用来指定要取出的内存单元大小的关键词,它表示要取出 4 字节的值。


  1. pop edi:将堆栈顶部的值弹出并存储到 EDI 寄存器中,以恢复被保存的 EDI 寄存器值。
  2. pop esi:将堆栈顶部的值弹出并存储到 ESI 寄存器中,以恢复被保存的 ESI 寄存器值。
  3. pop ebx:将堆栈顶部的值弹出并存储到 EBX 寄存器中,以恢复被保存的 EBX 寄存器值。 这些寄存器值在函数调用时被保存在堆栈中,以便在函数执行过程中可以使用堆栈来保存临时变量和函数调用的返回地址等信息。在函数执行结束时,需要将这些被保存的寄存器值恢复到原始状态,以确保程序的正确性和稳定性。需要注意的是,恢复寄存器的顺序应该与保存寄存器的顺序相反。
    pop出栈弹出:

image.png


  1. add esp, 0CCh:将堆栈指针增加 0xCC(204)个字节的大小,以清空堆栈中的函数参数和局部变量等信息。这是因为在函数执行过程中,函数所使用的堆栈空间需要被释放,以便其他函数可以使用该空间。
  2. cmp ebp, esp:比较 EBP 寄存器中的值(即堆栈底部的地址)和 ESP 寄存器中的值(即当前堆栈指针的地址),以确保堆栈指针在函数执行结束后正确地回到了调用函数之前的位置。如果堆栈指针没有正确回退,可能会导致程序崩溃或出现未定义的行为。
  3. call 00AE1244:调用位于地址 0x00AE1244 处的子程序(或函数)。该函数的具体作用需要根据函数地址和函数实现来确定。在函数执行结束后,程序将从函数调用的下一条指令继续执行。

销毁add函数:

  1. mov esp, ebp:将 EBP 寄存器中的值(即堆栈底部的地址)赋值给 ESP 寄存器,以恢复堆栈指针到调用该函数之前的位置。这是因为在函数执行过程中,EBP 寄存器被用作堆栈帧指针,指向当前函数的栈帧。而在函数调用结束后,需要将堆栈指针恢复到调用该函数之前的位置,以便程序可以正确地返回到调用该函数的位置继续执行。
  2. pop ebp:从堆栈中弹出一个值,并存储到 EBP 寄存器中,以恢复被保存的 EBP 寄存器值。在函数执行过程中,EBP 寄存器被用作堆栈帧指针,指向当前函数的栈帧。在函数执行结束后,需要将 EBP 寄存器的值恢复到调用该函数之前的值,以确保程序的正确性和稳定性。
    通过移动ebp,esp进行销毁并返回到main函数

00AE17BB ret 作用:这条汇编指令的作用是从当前函数中返回,并将控制权交还给调用该函数的代码。具体来说,ret 指令会从堆栈中弹出一个值,该值被认为是函数的返回地址,然后将程序计数器(PC)设置为该地址,从而使程序跳转到该地址并继续执行。在函数执行结束时,需要使用 ret 指令来返回到调用该函数的代码处,以便程序可以继续执行。需要注意的是,函数的返回值通常存储在 EAX 寄存器中,并在调用该函数的代码中使用。

image.png


  • add esp,8 将堆栈指针 esp 加上 8,即弹出堆栈中的两个参数。
  • mov dword ptr [ebp-20h],eax 将 EAX 中的值(即 Add 的返回值)存储到 [ebp-20h] 中,即将变量 ret 的值设置为 Add 的返回值。

  14: 	printf("%d\n", ret);
  1. mov eax,dword ptr [ebp-20h]:将位于 EBP 寄存器减去 0x20(即堆栈中函数参数和局部变量的偏移量)处的 4 个字节的值(也就是变量 ret 的值)加载到 EAX 寄存器中,以便将其打印出来。
  2. push eax:将 EAX 寄存器中的值(即变量 ret 的值)复制一份,并将其压入堆栈中,以便作为 printf 函数的第一个参数。
  3. push 0AE7B30h:将地址 0AE7B30h 压入堆栈中,以便作为 printf 函数的第二个参数,该地址指向格式化字符串 “%d\n”。 然后,call 00AE10CD 指令调用 printf 函数来打印 ret 的值。最后,add esp,8 指令将堆栈指针增加 8 个字节的大小,以清除堆栈中的两个参数,以便程序可以正常返回。在函数返回之前,xor eax,eax 指令将 EAX 寄存器清零,以便将其作为返回值返回给调用该函数的代码,并结束该函数的执行。

  1. call 00AE10CD:调用位于地址 0x00AE10CD 处的子程序(或函数),该函数为 printf 函数。在调用该函数之前,push 指令将两个参数(即变量 ret 的值和格式化字符串 %d\n 的地址)压入堆栈中。函数调用完成后,将会返回到下一条指令(即 add esp,8)继续执行。
  2. add esp,8:将堆栈指针增加 8 个字节的大小,以清除堆栈中的两个参数。这是因为在函数调用中,参数需要被压入堆栈中,并在函数执行完成后被清除,否则可能会导致堆栈溢出或其他未定义行为。在该函数调用完成后,程序将从调用该函数的下一条指令继续执行。

15: 	return 0;

00AE190E xor eax,eax
这条汇编指令的作用是将 EAX 寄存器中的值清零,即将其设置为 0。这通常用于将 EAX 寄存器作为函数的返回值,并且希望返回值为 0 的情况。在这个例子中,该指令用于将 EAX 寄存器清零,以便将其作为函数的返回值,并返回给调用该函数的代码。

16: }

总结

1.局部变量是怎么创建的?

局部变量一般是在函数的栈帧中创建的,即在函数执行期间分配在堆栈上的内存空间。在函数开始执行时,会为所有的局部变量分配内存空间,并将其地址存储在堆栈上。在函数执行结束后,这些变量占用的内存空间会被释放,以便其他函数可以使用。

2.为什么局部变量的值是随机值?

局部变量的值是随机的,是因为在声明变量时没有对其进行初始化。在栈帧中分配给局部变量的内存空间可能包含任意值,这些值是由之前使用这些内存空间的代码留下的,与当前函数的代码逻辑无关。因此,如果不对局部变量进行初始化,它们的值就会是随机的。
随机值就是我们刚开始设定的ccccccccc(打印可能会出现烫烫烫等)

3.函数是怎么传参的?传参的顺序是怎样的?

函数传参通常是通过堆栈来完成的,在调用函数之前,参数会被压入堆栈中。通常,参数的传递顺序是从右向左,即最后一个参数会被先压入堆栈中,第一个参数会被最后压入堆栈中。

4.形参和实参是什么关系?

形参和实参是函数传递参数的两个概念。形参是在函数定义中声明的变量,用于接收函数调用时传递的实参值。实参是在函数调用中传递给函数的值。在函数调用时,实参的值会被传递给形参,并在函数内部使用。
两者是独立的空间

5.函数调用是怎么做的?

函数调用是通过 call 汇编指令完成的。该指令将函数的返回地址压入堆栈,并将程序计数器设置为函数的入口地址,以便跳转到函数的起始位置。在函数执行结束后,使用 ret 指令返回到调用该函数的代码处。

6. 函数调用是结束后怎么返回的?

函数调用结束后,返回值通常存储在 EAX 寄存器中,并通过 ret 指令返回给调用该函数的代码。在返回之前,堆栈指针会被恢复,以便清除函数调用期间压入堆栈的局部变量和其他参数。如果需要返回多个值,可以将它们打包成一个数据结构(如结构体)并将该结构体的指针返回。

最后

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1. 人生最大的难度不是走出舒适区,而是保持冷静和自信面对不确定的未来

2. 人生就是一次次的选择,每个选择都会影响到未来的自己。

3. 没有比当下更好的时刻,因为未来和过去都只是自己的想象。

4. 人生中最重要的是学会珍惜当下,因为当下就是一切

5.划清和别人的界限。别人怎么看你,跟你毫无关系,你要怎么活,也跟别人没有任何关系,撇清别人,才能精力旺盛

最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!

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

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

相关文章

echarts双Y轴对齐;echarts堆叠柱状图;echarts数据集dataset结构;

提示&#xff1a;主要解决三个问题&#xff1a;1.echarts双Y轴设置和对齐 2.echarts堆叠柱状图 3.echarts数据集dataset结构 一、echarts双Y轴设置和对齐 1.双Y轴&#xff1a; yAxis两组数据&#xff0c;且series有组数据设置 yAxisIndex: 1 代表使用哪个Y轴的数据展示&#…

算法训练Day37:738.单调递增的数字 968.监控二叉树

文章目录 单调递增的数字题解 监控二叉树题解 单调递增的数字 CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsMedium (50.32%)3690--0 Tags 贪心 | 数学 Companies 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个…

【源码解析】多数据源 dynamic-datasource快速入门及源码解析

快速入门 依赖引入 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.1</version></dependency>配置文件 spring.datasource.druid.stat-view-se…

在不确定性时代,亚马逊云科技让企业拥有持续增长力

2023年3月29日「哈佛商业评论-未来管理沙龙」活动盛大启幕&#xff0c;此次沙龙活动以穿越周期的力量为主题方向&#xff0c;以解码跨国企业持续增长源动力为主旨&#xff0c;希望为企业高层管理者们带来更多思考和启迪。 作为特邀嘉宾&#xff0c;亚马逊全球副总裁、亚马逊云…

【数据库】数据库的基础知识

目录 前言 1、 查看数据库 1.1、查看所有数据库&#xff08;show databases;&#xff09; 1.2、创建数据库之后&#xff0c;查看创建的数据库的基本信息。 2、 创建数据库 2.1、直接创建数据库&#xff08;create database [数据库名];&#xff09; 2.2、创建数据库的时…

如何将b站缓存的m4s视频转换成mp4格式

阅读前请看一下&#xff1a;我是一个热衷于记录的人&#xff0c;每次写博客会反复研读&#xff0c;尽量不断提升博客质量。文章设置为仅粉丝可见&#xff0c;是因为写博客确实花了不少精力。希望互相进步谢谢&#xff01;&#xff01; 文章目录 阅读前请看一下&#xff1a;我是…

【Unity VR开发】结合VRTK4.0:瞬移

语录&#xff1a; 到不了的地方都叫做远方&#xff0c;回不去的世界都叫做家乡&#xff0c;我一直向往的却是比远更远的地方。 前言&#xff1a; 在VR场景中的移动主要有&#xff1a;瞬移和平移。瞬移相当于在虚拟世界中标记出目标位置&#xff0c;并自动传输到该位置&#xff…

【C++】5. 引用

文章目录 前言一、引用1.1 理解引用1.2 引用的特性1.3 引用的权限1.4 引用的使用场景1.4.1 做参数1.4.2 做返回值 1.5 引用的本质 前言 C语言中什么最难学&#xff1f;那当然就是指针了。不但使用起来麻烦&#xff0c;时不时还会产生一些意料之外的错误。C提供了一种方式&…

Direct local .aar file dependencies are not supported when building an AAR.

前言 起因&#xff1a;项目中含有视频播放功能&#xff0c;使用的是GSYVideoPlayer&#xff0c;因为公司网络问题经常依赖添加不了&#xff0c;所以将关于它的aar包全部下载下来直接本地依赖。 因为多个业务都可能涉及视频播放功能&#xff0c;为了复用&#xff0c;就想着将视频…

XTDrone PX4 仿真平台|使用Docker快速部署仿真环境

XTDrone PX4 仿真平台|使用Docker快速部署仿真环境 Docker简介NVIDIA驱动安装NVIDIA-Docker安装Docker镜像下载与使用Docker与宿主机建立ROS通信宿主机安装 XTDrone 源码 宿主机系统环境Ubuntu20.04 Docker简介 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包…

TestNG 中使用 Guice 来进行依赖注入

Guice是Google开发的一个轻量级&#xff0c;基于Java5&#xff08;主要运用泛型与注释特性&#xff09;的依赖注入框架(IOC)。 Guice非常小而且快。Guice是类型安全的&#xff0c;它能够对构造函数&#xff0c;属性&#xff0c;方法&#xff08;包含任意个参数的任意方法&…

3.微服务项目实战---Nacos Discovery--服务治理

3.1 服务治理介绍 先来思考一个问题 通过上一章的操作&#xff0c;我们已经可以实现微服务之间的调用。但是我们把服务提供者的网络地址 &#xff08; ip &#xff0c;端口&#xff09;等硬编码到了代码中&#xff0c;这种做法存在许多问题&#xff1a; 一旦服务提供者地址…

什么?Python一行命令快速搭建HTTP服务器并公网访问?

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转载自远程内网穿透的文章&#xff1a;【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透…

【C++入门必备知识:内联函数+指针空值nullptr】

【C入门必备知识&#xff1a;内联函数指针空值nullptr】 ①.内联函数Ⅰ.概念Ⅱ.宏与内联Ⅲ.总结 ②.指针空值nullptr(C11)Ⅰ.C98中的指针空值Ⅱ.注意&#xff1a; ①.内联函数 Ⅰ.概念 用inline修饰的函数就叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方将函数…

密歇根大学,一个被低估的美国公立常春藤名校

密歇根大学&#xff08;University of Michigan&#xff09;创建于1817年&#xff0c;是美国历史最悠久的公立大学之一&#xff0c;被誉为“公立常春藤”和“公立大学的典范”&#xff0c;与加州大学伯克利分校和威斯康星大学麦迪逊分校等大学一起代表了美国公立大学的最高水平…

Unity Camera -- (1)概览

Camera章节笔记所用的资源包在这里&#xff1a; https://connect-prd-cdn.unity.com/20230208/a0898204-bc36-4d6e-a3b2-d4b83ae67c1d/CreativeCore_Camera_2021.3LTS.ziphttps://connect-prd-cdn.unity.com/20230208/a0898204-bc36-4d6e-a3b2-d4b83ae67c1d/CreativeCore_Came…

ERTEC200P-2 PROFINET设备完全开发手册(9-1)

9. PROFIDRIVE AC1/AC4参考代码 PROFIdrive是西门子 Profibus 与 Profinet 两种通讯方式针对驱动的生产与自动化控制应用的一种协议框架&#xff0c;也可以称作“行规”&#xff0c; PROFIdrive使得用户更快捷方便实现对驱动的控制。PROFIdrive的最大特点是互操作性 – 不同厂…

低代码平台名声臭,用起来却真香——60%开发者不敢承认

群体盲从意识会淹没个体的理性&#xff0c;个体一旦将自己归入该群体&#xff0c;其原本独立的理性就会被群体的无知疯狂所淹没。——《乌合之众》 不知道从什么时候开始&#xff0c;“低代码不行”的论调充斥着整个互联网圈子&#xff0c;csdn、掘金、知乎、B站、脉脉……到处…

遗传算法求取函数最值问题

目录 1. 关于遗传算法 2. 遗传算法的步骤 3. 代码实现 3.1 工具函数 3.1.1 目标函数 3.1.2 解码 3.1.3 交叉 3.1.4 变异 3.2 主函数部分 3.3 代码 4. 其他 1. 关于遗传算法 遗传算法是根据生物进化论提出的计算最优解的一种算法&#xff0c;核心思想是物竞天择&…

九龙证券|光模块概念股封单资金超3亿元,传媒板块涨停潮来袭

今天A股三大股指低开低走。沪深两市收盘共37股涨停。剔除4只ST股&#xff0c;合计33股涨停。另外&#xff0c;10股封板未遂&#xff0c;整体封板率为78.72%。 涨停战场&#xff1a; 华工科技封单资金超3亿元 从收盘涨停板封单量来看&#xff0c;同方股份封单量最高&#xff0…