五、c++学习(加餐1:汇编基础学习)

news2024/10/7 6:48:39

经过前面几节课的学习,我们在一些地方都会使用汇编来分析,我们学习汇编,只是学习一些基础,主要是在我们需要深入分析语法的时候,使用汇编分析,这样会让我们更熟悉c++编译器和语法。

从这节课开始,会持续加餐,争取把类对象模型,分析出来。好了,不多说了,我们继续。

C++学习,b站直播视频

文章目录

    • 5.1 x86汇编介绍
      • 5.1.1 Intel汇编
      • 5.1.2 AT&T汇编
    • 5.2 x86寄存器介绍
      • 5.2.1 Intel寄存器解读
      • 5.2.2 Intel对数据的描述
      • 5.2.3 寄存器作用描述
    • 5.3 x86常用指令
      • 5.3.1 mov 传送指令
      • 5.3.2 push&pop
      • 5.3.3 lea取地址
      • 5.3.4 跳转语句
      • 5.3.5 函数调用&返回
      • 5.3.6 算术运算
      • 5.3.7 位运算指令
      • 5.3.8 附加:rep stos
    • 5.4 AT&T汇编分析
    • 5.5 Intel vs AT&T
      • 5.5.1 操作数长度
      • 5.5.2 寄存器命名
      • 5.5.3 立即数
      • 5.5.4 操作数的方向
      • 5.5.5 内存单元操作数
      • 5.5.6 间接寻址方式
      • 5.5.7 总结
    • 5.6 函数调用详解
      • 5.6.1 函数参数约定
        • 5.6.1.1 Intel方式
        • 5.6.1.2 AT&T方式
      • 5.6.2 函数的栈帧
        • 5.6.2.1 Intel方式
        • 5.6.2.2 AT&T方式
      • 5.6.3 函数返回值
        • 5.6.3.1 Intel方式
        • 5.6.3.2 AT&T方式
      • 5.6.4 函数退出
        • 5.6.4.1 Intel方式
        • 5.6.4.2 AT&T方式
    • 5.7 扩展:ARM平台汇编分析
      • 5.7.1 RISC与CISC的区别
      • 5.7.2 ARM寄存器解读
      • 5.7.3 ARM对数据的描述
      • 5.7.4 ARM汇编浅析

5.1 x86汇编介绍

5.1.1 Intel汇编

Windows派系的汇编代码,VS就是我们一直用的这个IDE,就是这种风格的。不知道clang是什么风格,clang占的内存较大,我就不安装了,有条件的同学,可以自行测试。

后面几节课都是使用vs2022来分析的,所以这种汇编风格,我们是需要学习的。可以先来看看:

int func(int i)
{
00007FF72DF617E0 89 4C 24 08          mov         dword ptr [rsp+8],ecx  
00007FF72DF617E4 55                   push        rbp  
00007FF72DF617E5 57                   push        rdi  
00007FF72DF617E6 48 81 EC E8 00 00 00 sub         rsp,0E8h  
00007FF72DF617ED 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF72DF617F2 48 8D 0D 6E F8 00 00 lea         rcx,[00007FF72DF71067h]  
00007FF72DF617F9 E8 54 FB FF FF       call        00007FF72DF61352  
	return 2 * i;
00007FF72DF617FE 8B 85 E0 00 00 00    mov         eax,dword ptr [rbp+00000000000000E0h]  
00007FF72DF61804 D1 E0                shl         eax,1  
}
00007FF72DF61806 48 8D A5 C8 00 00 00 lea         rsp,[rbp+00000000000000C8h]  
00007FF72DF6180D 5F                   pop         rdi  
00007FF72DF6180E 5D                   pop         rbp  
00007FF72DF6180F C3                   ret  

int main()
{
00007FF72DF61830 40 55                push        rbp  
00007FF72DF61832 57                   push        rdi  
00007FF72DF61833 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF72DF6183A 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF72DF6183F 48 8D 0D 21 F8 00 00 lea         rcx,[00007FF72DF71067h]  
00007FF72DF61846 E8 07 FB FF FF       call        00007FF72DF61352  
	// std::cout << "Hello World!\n";

	int s = func(255);
00007FF72DF6184B B9 FF 00 00 00       mov         ecx,0FFh  
00007FF72DF61850 E8 82 F8 FF FF       call        00007FF72DF610D7  
00007FF72DF61855 89 45 04             mov         dword ptr [rbp+4],eax  
	return 0;
00007FF72DF61858 33 C0                xor         eax,eax  
}
00007FF72DF6185A 48 8D A5 E8 00 00 00 lea         rsp,[rbp+00000000000000E8h]  
00007FF72DF61861 5F                   pop         rdi  
00007FF72DF61862 5D                   pop         rbp  
00007FF72DF61863 C3                   ret  

5.1.2 AT&T汇编

AT&T汇编:读作“AT and T”,是 American Telephone & Telegraph 的缩写,是在贝尔实验室多年运营。

主要使用的地方就是GCC,Objdump。(这个我们学linux就知道了)。

主要使用的平台:Linux、Unix、Max OS、iOS(模拟器)

同一份代码,我们在linux下看看:

0000000000401146 <_Z4funci>:
  401146:	55                   	push   %rbp
  401147:	48 89 e5             	mov    %rsp,%rbp
  40114a:	89 7d fc             	mov    %edi,-0x4(%rbp)
  40114d:	8b 45 fc             	mov    -0x4(%rbp),%eax
  401150:	01 c0                	add    %eax,%eax
  401152:	5d                   	pop    %rbp
  401153:	c3                   	retq   

0000000000401154 <main>:
  401154:	55                   	push   %rbp
  401155:	48 89 e5             	mov    %rsp,%rbp
  401158:	48 83 ec 10          	sub    $0x10,%rsp
  40115c:	bf ff 00 00 00       	mov    $0xff,%edi
  401161:	e8 e0 ff ff ff       	callq  401146 <_Z4funci>
  401166:	89 45 fc             	mov    %eax,-0x4(%rbp)
  401169:	b8 00 00 00 00       	mov    $0x0,%eax
  40116e:	c9                   	leaveq 
  40116f:	c3                   	retq 

感觉还是g++的风格简单些。

5.2 x86寄存器介绍

在这里插入图片描述

这个图来自《深入理解计算机系统》。

这些寄存器都是x86体系下的寄存器,当然x86的寄存器还有很多,我下载过intel的芯片手册,都是英文,看的头都大了。我们只要了解这些就可以了。当然x86还有存浮点数的寄存器,这个没有了解,就不说,上面这个图,才是我们重点。注意最后面注释介绍的是AT&T。

5.2.1 Intel寄存器解读

Intel的寄存器有一个很有意思的地方,就是因为要兼容以前的,所以寄存器的位数从8位到16位到32位再到64位都有,我们看上面第一个寄存器RAX:

64位叫:rax

32位叫:eax

16位叫:ax

8位叫:al

所以我们待会看汇编的时候,就会看到,不过一般会看到rax和eax,现在16位和8位已经被淘汰了。

5.2.2 Intel对数据的描述

Intel作为业界的老大哥,背负的历史包袱过重,哈哈哈。

c声明Intel数据类型
char字节
short
int双字
long四字

总感觉当年16位的时候,觉得够大了吧,所以叫字,然后int出来之后,懵逼了,只能叫双字,这个其实不用记,只是简单介绍一下。(到时候可以看看ARM,没有历史负担就是任性)

5.2.3 寄存器作用描述

为啥是简单描述,是因为两种汇编风格对函数调用的传参不一样,这就有点难搞,哈哈哈。

在这里插入图片描述

图片引用了https://blog.csdn.net/c2682736/article/details/122349851这个链接的。

特殊用处寄存器介绍:

rax:函数返回值

rdx:在Intel方式中做第二个参数,在AT&T中做第三个参数,甚至还可以做第二个返回值寄存器。

rsp:栈顶指针,linux是满减栈,看着windows是往上加的栈

rbp:栈底指针,两个指针一减,就可以算出栈大小。

rip:指令寄存器

函数参数寄存器:这个因为两种方式不一样,所以在后面再详解。

5.3 x86常用指令

上面介绍了寄存器,接着我们介绍一下常用指令,指令+寄存器的操作,就相当于我们的一条语句。不过这个指令,不需要记,也记不住,说实话,看到不懂再查询。

5.3.1 mov 传送指令

// 我们先开始看mov命令,这个命令是出常见的,也是我们c++使用的赋值语句。
     int s = 1;
00007FF72F8E562D C7 45 04 01 00 00 00 mov         dword ptr [rbp+4],1  
    // rbp是栈基地址,rbp+4就是s的地址,mov就是把1赋值到[rbp+4] (dword就是双字)
    // []是对这个地址寻址,也就是*(rbp+4)的意思
	int s1 = s;
00007FF72F8E5634 8B 45 04             mov         eax,dword ptr [rbp+4]  
    // 这是两个变量赋值,两个变量赋值,需要借助寄存器了,eax.
    // 第一次,先把[rbp+4]赋值eax寄存器
00007FF72F8E5637 89 45 24             mov         dword ptr [rbp+24h],eax
    // 第二次,再把这个寄存器eax赋值到[rbp+24h],[rbp+24]就是s1的地址
	int* ps = &s;
00007FF72F8E563A 48 8D 45 04          lea         rax,[rbp+4]  
    // lea是我们后面介绍的,取地址指令,把[rbp+4]的地址赋值到rax中
00007FF72F8E563E 48 89 45 48          mov         qword ptr [rbp+48h],rax 
    // 然后在把rax的值赋值到[rbp+48h]中,(qword是四字,指针在64位系统都是8字节)
	*ps = 111;
00007FF72F8E5642 48 8B 45 48          mov         rax,qword ptr [rbp+48h]  
    // 把ps的值赋值到rax(因为ps是指针,所以这个值其实就是地址)
00007FF72F8E5646 C7 00 6F 00 00 00    mov         dword ptr [rax],6Fh 
    // 然后对rax就是指向的内存就行赋值,[]就是寻址的意思

5.3.2 push&pop

这两个命令一起来,很明显push是压栈,pop是出栈,只有调用函数才有这个操作。

我们来看看:

int test()
{
00007FF7486817D0 40 55                push        rbp  
    // 这是把上一个函数的栈基地址保存到栈里,具体的函数栈帧我们后面分析
00007FF7486817D2 57                   push        rdi  
    // rdi没记错应该是第一个参数,我也不清楚为啥要入栈????
00007FF7486817D3 48 81 EC E8 00 00 00 sub         rsp,0E8h  
   // 这句也是我疑惑的地方,不是不知道这句意思,是不知道为啥windos栈能隔这么大。
    // 这句意思就是把栈指针往下减0E8h这么大空间
00007FF7486817DA 48 8D 6C 24 20       lea         rbp,[rsp+20h]
    // 然后在把rsp往上加20h作为这个函数的栈空间,这个我试过了,如果变量过大,这个空间也会变大,等下我们试试
00007FF7486817DF 48 8D 0D 81 F8 00 00 lea         rcx,[00007FF748691067h]  
00007FF7486817E6 E8 67 FB FF FF       call        00007FF748681352  
    // 后面这两句我更疑惑了,调用了一个函数,就不分析这个函数干啥了。
	return 0;
00007FF7486817EB 33 C0                xor         eax,eax  
    // xor是后面的指令,意思就是异或,反正两个都是0
}
00007FF7486817ED 48 8D A5 C8 00 00 00 lea         rsp,[rbp+00000000000000C8h]  
    // rbp+C8h其实就是等于rsp原来指向的地址,因为上面rbp=rsp+20h,这个20h+C8h=E8h,就是当初rsp减少的空间
00007FF7486817F4 5F                   pop         rdi  
    // 然后再把rdi从栈中弹出到rdi寄存器里
00007FF7486817F5 5D                   pop         rbp  
    // 上面把rsp还原了,这里肯定要把rbp也还原
00007FF7486817F6 C3                   ret 

5.3.3 lea取地址

这个上面说了,就不继续说,用处就是取地址。

5.3.4 跳转语句

	int s2 = 0;
00007FF650C34671 C7 45 64 00 00 00 00 mov         dword ptr [rbp+64h],0  
	if (s == 1)
00007FF650C34678 83 7D 04 01          cmp         dword ptr [rbp+4],1  
        // cmp就是比较语句,比较这两个值是否想等
        // 如果operand1等于operand2,则ZF被置为1;否则,ZF被置为0。如果operand1小于operand2,则SF被置为1;否则,SF被置为0。如果操作过程中有进位,则CF被置为1;否则,CF被置为0。
00007FF650C3467C 75 07                jne         00007FF650C34685  
        // jne是判断上一条语句不等于0(即ZF标志位为0),两个不想等ZF为0才跳转
        // 还有其他跳转命令je jle  jl jg等等
	{
		s2 = 1;
00007FF650C3467E C7 45 64 01 00 00 00 mov         dword ptr [rbp+64h],1  
	}

	s2 = 2;
00007FF650C34685 C7 45 64 02 00 00 00 mov         dword ptr [rbp+64h],2 
    // 后面就不解释了

5.3.5 函数调用&返回

	// 测试push pop
	test();
00007FF650C3466C E8 54 CD FF FF       call        00007FF650C313C5 

call 函数调用&ret 返回这也比较常见了。

5.3.6 算术运算

  • add 想加
  • sub 相减
  • inc 加1
  • mul 相乘
  • div 相除
	int s4 = s3 + 4;
00007FF63E6F4C66 8B 85 84 00 00 00    mov         eax,dword ptr [rbp+0000000000000084h]  
00007FF63E6F4C6C 83 C0 04             add         eax,4  

其他的就不演示了。

5.3.7 位运算指令

  • and 与运算
  • or 或运算
  • xor 异或运算
  • not 取反

不演示了。

5.3.8 附加:rep stos

	int s5[20] = { 0 };
00007FF6BD944C75 48 8D 85 D0 00 00 00 lea         rax,[rbp+00000000000000D0h] 
    // 把数组的地址赋值到rax中
00007FF6BD944C7C 48 8B F8             mov         rdi,rax  
    // 然后在赋值到rdi中
00007FF6BD944C7F 33 C0                xor         eax,eax  
    // 然后把eax清0
00007FF6BD944C81 B9 50 00 00 00       mov         ecx,50h  
    // 50h是这个数组的大小
00007FF6BD944C86 F3 AA                rep stos    byte ptr [rdi] 

这个还挺常用的,用在一个结构体或类或数组,一些符合数据结构置0的时候。

后面的rep stos是我们重点,这其实是两个指令复合使用。

REP(Repeat)汇编指令是一种重复执行指令的控制指令,它通常和其他指令组合使用,用于在处理字符串或数组时进行重复操作。

REP指令可以和多个其他指令搭配使用,如MOV、STOS等。其语法如下:

rep instruction

其中,instruction是要重复执行的指令。REP指令会将ECX寄存器中的值作为计数器,循环执行instruction指定的操作。每次循环都会将ECX减1,直到ECX的值为0为止。

还记得刚刚ecx赋值了50h

STOS指令:将AL/AX/EAX的值存储到[EDI]指定的内存单元

STOS指令使用AL(字节 - STOSB),AX(字 - STOSW)或EAX(对于双 - STOSD的)数据复制目标字符串,在内存中通过ES:DI指向。

我们这个数组是int,是双子,所以使用eax,我们前面不是刚用eax异或就是为了清0.

所以这个句话就是把数组清0了。

5.4 AT&T汇编分析

上面我们是对windos下的Intel汇编格式的分析,下面我们就分析linux下的AT&T汇编格式,这里就不在区分了,我们直接看了,看完了,我们下一节,就可以总结两种方式的差异。

// 编译是需要加-g,objdump -S 才能反汇编出带源码   
0000000000401146 <_Z4testv>:

#include <iostream>


int test()
{
  401146:	55                   	push   %rbp
  401147:	48 89 e5             	mov    %rsp,%rbp
	return 0;
  40114a:	b8 00 00 00 00       	mov    $0x0,%eax
}
  40114f:	5d                   	pop    %rbp
  401150:	c3                   	retq   
      // retq就是退出
      // linux的函数风格我比较喜欢,因为比较简单,不像windos那么复杂

0000000000401151 <main>:

int main()
{
  401151:	55                   	push   %rbp
      // 把栈基地址入栈,寄存器是用%
  401152:	48 89 e5             	mov    %rsp,%rbp
      // 把新的rsp地址移动到rbp,AT&T的mov源操作数是在中间
  401155:	48 83 ec 70          	sub    $0x70,%rsp
       // 把rsp栈的大小设置为0x70h,这么大
	int s = 1;
  401159:	c7 45 e0 01 00 00 00 	movl   $0x1,-0x20(%rbp)
      // l其实就是int,rbp-20h就是s的地址,原来这就是满减栈的意思,window是栈是往上加
	int s1 = s;
  401160:	8b 45 e0             	mov    -0x20(%rbp),%eax
      // 如果不看源码,这个确实不好理解,就是把rbp-20h的值赋值给eax
  401163:	89 45 fc             	mov    %eax,-0x4(%rbp)
      // 然后再把eax的值赋值到s1,这里有点奇怪为啥s1的地址是rbp-4h??
	int* ps = &s;
  401166:	48 8d 45 e0          	lea    -0x20(%rbp),%rax
      // lea取地址,熟悉不,就是把s的地址赋值到rax中
  40116a:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
      // 然后再在rax赋值到ps里,这里ps的地址是rbp-10h
	*ps = 111;
  40116e:	48 8b 45 f0          	mov    -0x10(%rbp),%rax
  401172:	c7 00 6f 00 00 00    	movl   $0x6f,(%rax)
      // 这个也是赋值,这个()也是寻址意思,跟Intel的[]是一个意思

	// 测试push pop
	test();
  401178:	e8 c9 ff ff ff       	callq  401146 <_Z4testv>
      // 调用也是用call,不过加了个后缀

	int s2 = 0;
  40117d:	c7 45 ec 00 00 00 00 	movl   $0x0,-0x14(%rbp)
	if (s == 1)
  401184:	8b 45 e0             	mov    -0x20(%rbp),%eax
  401187:	83 f8 01             	cmp    $0x1,%eax
  40118a:	75 07                	jne    401193 <main+0x42>
      // 这个cmp和jne就一样了
	{
		s2 = 1;
  40118c:	c7 45 ec 01 00 00 00 	movl   $0x1,-0x14(%rbp)
	}

	s2 = 2;
  401193:	c7 45 ec 02 00 00 00 	movl   $0x2,-0x14(%rbp)


	int s3 = 1 + 4;
  40119a:	c7 45 e8 05 00 00 00 	movl   $0x5,-0x18(%rbp)
	int s4 = s3 + 4;
  4011a1:	8b 45 e8             	mov    -0x18(%rbp),%eax
  4011a4:	83 c0 04             	add    $0x4,%eax
  4011a7:	89 45 e4             	mov    %eax,-0x1c(%rbp)
	// add也是一样的
      
	int s5[20] = { 0 };
  4011aa:	48 c7 45 90 00 00 00 	movq   $0x0,-0x70(%rbp)
  4011b1:	00 
  4011b2:	48 c7 45 98 00 00 00 	movq   $0x0,-0x68(%rbp)
  4011b9:	00 
  4011ba:	48 c7 45 a0 00 00 00 	movq   $0x0,-0x60(%rbp)
  4011c1:	00 
  4011c2:	48 c7 45 a8 00 00 00 	movq   $0x0,-0x58(%rbp)
  4011c9:	00 
  4011ca:	48 c7 45 b0 00 00 00 	movq   $0x0,-0x50(%rbp)
  4011d1:	00 
  4011d2:	48 c7 45 b8 00 00 00 	movq   $0x0,-0x48(%rbp)
  4011d9:	00 
  4011da:	48 c7 45 c0 00 00 00 	movq   $0x0,-0x40(%rbp)
  4011e1:	00 
  4011e2:	48 c7 45 c8 00 00 00 	movq   $0x0,-0x38(%rbp)
  4011e9:	00 
  4011ea:	48 c7 45 d0 00 00 00 	movq   $0x0,-0x30(%rbp)
  4011f1:	00 
  4011f2:	48 c7 45 d8 00 00 00 	movq   $0x0,-0x28(%rbp)
  4011f9:	00 
		// 看来linux下没有rep stos这个命令,在使用笨方式一个一个清0
	return 0;
  4011fa:	b8 00 00 00 00       	mov    $0x0,%eax
}
  4011ff:	c9                   	leaveq 
  401200:	c3                   	retq 

5.5 Intel vs AT&T

上面我们分析过了Intel和AT&T的汇编,然后我们来总结一下,这两种汇编的区别。

5.5.1 操作数长度

我们操作一个数的大小,其实可以通过在汇编指令上体现出来的。

AT&T的操作码后面有一个后缀,其含义就是操作码的大小。

(后缀分别为:b、w、l、q)

在Intel的语法中,则要在内存单元操作数的前面加上前缀。

(前缀分别为:byte ptr、world ptr、dworld ptr、qworld ptr)

c声明Intel数据类型Intel语法AT&T语法大小
char字节mov al,blmovb %bl,%al1
shortmov ax,bxmovw %bx,%ax2
int双字mov eax,dworld ptr[ebx]movl (%ebx),%eax4
long四字mov rax, qworld ptr[rbx]movq (%rbx),%rax8
float单精度不知道后缀s4
char *四字不知道后缀q8
double双精度不知道后缀l8

我对汇编指令操作浮点确实不熟悉,因为我们自己分析的大部分都是整数,以后如果碰到了,操作浮点的,再回来补充补充。

5.5.2 寄存器命名

通过上面的例子,应该都发现了,寄存器命名两种方式的汇编不一样。

在Intel的语法中,寄存器是不需要前缀的。

在AT&T的语法中,寄存器是需要加%前缀

Intel语法AT&T语法
mov rax, qworld ptr[rbx]movq (%rbx),%rax

5.5.3 立即数

立即数的写法也不一样。

在Intel语法中,十六进制和二进制立即数后面分别冠以h和b

在AT&T语法中,立即数前面加上$,然后如果是十六进制前面再加上0x

Intel语法AT&T语法
mov eax,8movl **$**8,%eax
mov ebx,0ffffhmovl **$**0xffff,%ebx

5.5.4 操作数的方向

这一点我就好奇,为啥搞的方向相反,这样看起代码,真的难受。

Intel与AT&T操作数的方式正好相反。

在Intel语法中,第一个操作数是目的操作数,第二个操作数是源操作数

在AT&T中,第一个数是源操作数,第二个数是目的操作数

Intel语法AT&T语法
mov eax,[ecx]movl (%ecx),%eax

5.5.5 内存单元操作数

从上面例子可以看出,内存操作数也有所不同。

在Intel的语法中,基寄存器用"[]"括起来。

在AT&T中,基寄存器用"()"括起来

Intel语法AT&T语法
mov eax,[ecx]movl (%ecx),%eax

5.5.6 间接寻址方式

与Intel的语法比较,AT&T间接寻址方式可能更晦涩难懂一些。

Intel的指令格式是:
segreg:[base+index*scale+disp]
AT&T的指令格式是:
%segreg:disp(base,index,scale)

格式解读:

  • 其中index/scale/disp/segreg 全部可选的,完全可以简化掉。
  • 如果没有指定scale而指定了index,则scale的缺省值为1.
  • segreg段寄存器依赖于指令以及应用程序是运行在实模式还是保护模式下。在实模式下它依赖于指令,在保护模式下segreg是多余的。
  • 在AT&T中,当立即数用在scale/disp中时,不应当在其前冠以$前缀
Intel语法
指令segrg:[base+index*scale+disp]
AT&T语法
指令%segreg:disp(base,index,scale)
mov eax,[ebx+10h]movl 0x10(%ebx),%eax
add eax,[ebx+ecx*2h]addl (%ebx,%ecx,0x2),%eax
lea eax,[ebx+ecx]leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h]subl -0x20(%eax,%ecx,0x4), %eax

5.5.7 总结

项目IntelAT&T说明
操作数长度mov rax, qworld ptr[rbx]movq (%rbx),%rax一个加前缀,一个加后缀
寄存器命名mov rax, qworld ptr[rbx]movq (%rbx),%raxAT&T需要加%
立即数mov ebx,0ffffhmovl **$**0xffff,%ebxAT&T需要加$
操作数的方向mov eax,[ecx]movl (%ecx),%eax刚好相反
内存单元操作数mov eax,[ecx]movl (%ecx),%eax一个使用[],一个使用()
间接寻址方式segreg:[base+index*scale+disp]%segreg:disp(base,index,scale)其实都是Intel的方式计算

5.6 函数调用详解

前面吹了这么多水,终于来到我们这一篇的重点了,趁着我们刚学习汇编,还热乎,我们来分析一下函数是怎么调用的。

5.6.1 函数参数约定

我们调用函数的时候,会进行函数传参,那函数是怎么知道我们传了几个参数,并且每个参数的值是多少??

其实这都是约定好的,要命的是不同平台约定的不一样,真是离了大谱,增加了学习成本。

5.6.1.1 Intel方式

我们用什么方法来看这个约定的方式呢?其实很简单,我们直接写10个参数,哈哈哈哈。

	test1(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
00007FF6EDCA1958 C7 44 24 48 0A 00 00 00 mov         dword ptr [rsp+48h],0Ah  
00007FF6EDCA1960 C7 44 24 40 09 00 00 00 mov         dword ptr [rsp+40h],9  
00007FF6EDCA1968 C7 44 24 38 08 00 00 00 mov         dword ptr [rsp+38h],8  
00007FF6EDCA1970 C7 44 24 30 07 00 00 00 mov         dword ptr [rsp+30h],7  
00007FF6EDCA1978 C7 44 24 28 06 00 00 00 mov         dword ptr [rsp+28h],6  
00007FF6EDCA1980 C7 44 24 20 05 00 00 00 mov         dword ptr [rsp+20h],5  // 第5个参数开始入栈了
00007FF6EDCA1988 41 B9 04 00 00 00    mov         r9d,4  // 第四个参数r9
00007FF6EDCA198E 41 B8 03 00 00 00    mov         r8d,3  // 第三个参数r8
00007FF6EDCA1994 BA 02 00 00 00       mov         edx,2  // 第二个参数rdx
00007FF6EDCA1999 B9 01 00 00 00       mov         ecx,1  // 第一个参数rcx
00007FF6EDCA199E E8 7D F9 FF FF       call        00007FF6EDCA1320 

是不是一看汇编就明白了,这里我们就分析整数,浮点不做分析,后面有兴趣可以自行分析。

  • rcx,rdx,r8,r9:用来存储整数或指针参数,安装从左到右顺序
  • xmm0,1,2,3 用来存储浮点参数
  • 其余参数会压入栈中。

5.6.1.2 AT&T方式

我们接下来看看AT&T方式:

	test1(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  40125c:	6a 0a                	pushq  $0xa       // 第10个参数
  40125e:	6a 09                	pushq  $0x9       // 第九个参数
  401260:	6a 08                	pushq  $0x8       // 第八个参数
  401262:	6a 07                	pushq  $0x7       // 第七个参数
  401264:	41 b9 06 00 00 00    	mov    $0x6,%r9d  // 第六个参数
  40126a:	41 b8 05 00 00 00    	mov    $0x5,%r8d  // 第五个参数
  401270:	b9 04 00 00 00       	mov    $0x4,%ecx  // 第四个参数
  401275:	ba 03 00 00 00       	mov    $0x3,%edx  // 第三个参数
  40127a:	be 02 00 00 00       	mov    $0x2,%esi  // 第二个参数
  40127f:	bf 01 00 00 00       	mov    $0x1,%edi  // 第一个参数
  401284:	e8 c8 fe ff ff       	callq  401151 <_Z5test1iiiiiiiiii>

这个也会看下汇编就懂了,下面是总结:

  • 当参数在 6 个以内,参数从左到右依次放入寄存器: rdi, rsi, rdx, rcx, r8, r9。(多了rdi,rsi)
  • 当参数大于 6 个, 大于六个的部分的依次从 “右向左” 压入栈中,和32位汇编一样。

5.6.2 函数的栈帧

通过上面认识到了函数参数,那我们来看看函数内部,这个函数的栈是怎么使用的。

5.6.2.1 Intel方式

int test1(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10)
{
00007FF6EDCA1810 44 89 4C 24 20       mov         dword ptr [rsp+20h],r9d  
00007FF6EDCA1815 44 89 44 24 18       mov         dword ptr [rsp+18h],r8d  
00007FF6EDCA181A 89 54 24 10          mov         dword ptr [rsp+10h],edx  
00007FF6EDCA181E 89 4C 24 08          mov         dword ptr [rsp+8],ecx  
    // 这是rsp把前面的4个参数入栈,前面4个存储在寄存器里的,需要入栈\
    // 我们来看看这里的rsp = 000000D318CFF808 rbp = 000000D318CFF860
    // 第一个参数 = 000000D318CFF808 + 8h  = 000000D318CFF810
    // 第二个参数 = 000000D318CFF808 + 10h = 000000D318CFF818
    // 第三个参数 = 000000D318CFF808 + 18h = 000000D318CFF820
    // 第10个参数 = 000000D318CFF808 + 48h = 000000D318CFF850
00007FF6EDCA1822 55                   push        rbp  
    // 
00007FF6EDCA1823 57                   push        rdi  
00007FF6EDCA1824 48 81 EC E8 00 00 00 sub         rsp,0E8h  
00007FF6EDCA182B 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF6EDCA1830 48 8D 0D 30 F8 00 00 lea         rcx,[00007FF6EDCB1067h]  
00007FF6EDCA1837 E8 25 FB FF FF       call        00007FF6EDCA1361  



	return i1 + i10;
00007FF6EDCA183C 8B 85 28 01 00 00    mov         eax,dword ptr [rbp+0000000000000128h]  
00007FF6EDCA1842 8B 8D E0 00 00 00    mov         ecx,dword ptr [rbp+00000000000000E0h]  
00007FF6EDCA1848 03 C8                add         ecx,eax  
00007FF6EDCA184A 8B C1                mov         eax,ecx  
}
00007FF6EDCA184C 48 8D A5 C8 00 00 00 lea         rsp,[rbp+00000000000000C8h]  
00007FF6EDCA1853 5F                   pop         rdi  
00007FF6EDCA1854 5D                   pop         rbp  
00007FF6EDCA1855 C3                   ret  

我把Intel的函数调用的栈帧画了一个图:

在这里插入图片描述

因为test1函数没有定义变量,所以函数栈里并没有值。如果定义了变量也会跟main函数一样,就是有点搞不懂,每个变量离的这么远是因为怕踩到内存????有知道的可以留言告诉我。

5.6.2.2 AT&T方式

0000000000401151 <_Z5test1iiiiiiiiii>:

int test1(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10)
{
  401151:	55                   	push   %rbp
  401152:	48 89 e5             	mov    %rsp,%rbp
  401155:	89 7d fc             	mov    %edi,-0x4(%rbp)
  401158:	89 75 f8             	mov    %esi,-0x8(%rbp)
  40115b:	89 55 f4             	mov    %edx,-0xc(%rbp)
  40115e:	89 4d f0             	mov    %ecx,-0x10(%rbp)
  401161:	44 89 45 ec          	mov    %r8d,-0x14(%rbp)
  401165:	44 89 4d e8          	mov    %r9d,-0x18(%rbp)
      // 这个会把其他参数都存进栈里
      



	return i1 + i10;
  401169:	8b 55 fc             	mov    -0x4(%rbp),%edx
  40116c:	8b 45 28             	mov    0x28(%rbp),%eax
  40116f:	01 d0                	add    %edx,%eax
}
  401171:	5d                   	pop    %rbp
  401172:	c3                   	retq

linux就比较节省内存,因为test1没有使用临时变量,所以栈大小是0。

在这里插入图片描述

5.6.3 函数返回值

我们都知道,函数可以返回一个值,那函数是怎么返回这个值的呢?我们继续分析

5.6.3.1 Intel方式

char test2()
{
00007FF7DFC51870 40 55                push        rbp  
00007FF7DFC51872 57                   push        rdi  
00007FF7DFC51873 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7DFC5187A 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7DFC5187F 48 8D 0D E1 F7 00 00 lea         rcx,[00007FF7DFC61067h]  
00007FF7DFC51886 E8 D6 FA FF FF       call        00007FF7DFC51361  
	char s = 9;
00007FF7DFC5188B C6 45 04 09          mov         byte ptr [rbp+4],9  
	return s;
00007FF7DFC5188F 0F B6 45 04          movzx       eax,byte ptr [rbp+4]  
    // 返回到char型
}
    
short test3()
{
00007FF7DFC519E0 40 55                push        rbp  
00007FF7DFC519E2 57                   push        rdi  
00007FF7DFC519E3 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7DFC519EA 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7DFC519EF 48 8D 0D 71 F6 00 00 lea         rcx,[00007FF7DFC61067h]  
00007FF7DFC519F6 E8 66 F9 FF FF       call        00007FF7DFC51361  
	short s = 9;
00007FF7DFC519FB B8 09 00 00 00       mov         eax,9  
00007FF7DFC51A00 66 89 45 04          mov         word ptr [rbp+4],ax  
	return s;
00007FF7DFC51A04 0F B7 45 04          movzx       eax,word ptr [rbp+4]  
}

int test4()
{
00007FF7DFC518B0 40 55                push        rbp  
00007FF7DFC518B2 57                   push        rdi  
00007FF7DFC518B3 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7DFC518BA 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7DFC518BF 48 8D 0D A1 F7 00 00 lea         rcx,[00007FF7DFC61067h]  
00007FF7DFC518C6 E8 96 FA FF FF       call        00007FF7DFC51361  
	int s = 9;
00007FF7DFC518CB C7 45 04 09 00 00 00 mov         dword ptr [rbp+4],9  
	return s;
00007FF7DFC518D2 8B 45 04             mov         eax,dword ptr [rbp+4]  
}

long long test5()
{
00007FF7873C18E0 40 55                push        rbp  
00007FF7873C18E2 57                   push        rdi  
00007FF7873C18E3 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7873C18EA 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7873C18EF 48 8D 0D 71 F7 00 00 lea         rcx,[00007FF7873D1067h]  
00007FF7873C18F6 E8 66 FA FF FF       call        00007FF7873C1361  
	long long s = 9;
00007FF7873C18FB 48 C7 45 08 09 00 00 00 mov         qword ptr [rbp+8],9  
	return s;
00007FF7873C1903 48 8B 45 08          mov         rax,qword ptr [rbp+8]  
}

实验证明,现在是64位系统了,没有数据类型比64位的长了,尴尬,所以测不了超过rax的时候,会不会用其他寄存器了。

5.6.3.2 AT&T方式

看下linux的返回值应该是一样的。

0000000000401173 <_Z5test2v>:

char test2()
{
  401173:	55                   	push   %rbp
  401174:	48 89 e5             	mov    %rsp,%rbp
	char s = 9;
  401177:	c6 45 ff 09          	movb   $0x9,-0x1(%rbp)
	return s;
  40117b:	0f b6 45 ff          	movzbl -0x1(%rbp),%eax
}
  40117f:	5d                   	pop    %rbp
  401180:	c3                   	retq   

0000000000401181 <_Z5test3v>:

short test3()
{
  401181:	55                   	push   %rbp
  401182:	48 89 e5             	mov    %rsp,%rbp
	short s = 9;
  401185:	66 c7 45 fe 09 00    	movw   $0x9,-0x2(%rbp)
	return s;
  40118b:	0f b7 45 fe          	movzwl -0x2(%rbp),%eax
}
  40118f:	5d                   	pop    %rbp
  401190:	c3                   	retq   

0000000000401191 <_Z5test4v>:

int test4()
{
  401191:	55                   	push   %rbp
  401192:	48 89 e5             	mov    %rsp,%rbp
	int s = 9;
  401195:	c7 45 fc 09 00 00 00 	movl   $0x9,-0x4(%rbp)
	return s;
  40119c:	8b 45 fc             	mov    -0x4(%rbp),%eax
}
  40119f:	5d                   	pop    %rbp
  4011a0:	c3                   	retq   

00000000004011a1 <_Z5test5v>:

long long test5()
{
  4011a1:	55                   	push   %rbp
  4011a2:	48 89 e5             	mov    %rsp,%rbp
	long long s = 9;
  4011a5:	48 c7 45 f8 09 00 00 	movq   $0x9,-0x8(%rbp)
  4011ac:	00 
	return s;
  4011ad:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
}
  4011b1:	5d                   	pop    %rbp
  4011b2:	c3                   	retq 

看来64位系统就是这么强大,不需要两个寄存器了,直接一个寄存器就可以返回值成功了。

5.6.4 函数退出

函数退出就比较简单了。

5.6.4.1 Intel方式

00007FF7873C1907 48 8D A5 E8 00 00 00 lea         rsp,[rbp+00000000000000E8h]  
    // 处理rsp的值,这样计算就是得到前面函数的rsp
00007FF7873C190E 5F                   pop         rdi
    // 还记得rdi进栈不,现在就弹出
00007FF7873C190F 5D                   pop         rbp
    // 也继续弹出rbp
00007FF7873C1910 C3                   ret  
    // ret指令是一种汇编指令,它的作用是从一个子程序中返回到调用该子程序的主程序。在执行时,ret指令将从堆栈中弹出返回地址,并跳转到该地址继续执行主程序。(还记得下一个执行的地址,也入栈了吧)

5.6.4.2 AT&T方式

  4011b1:	5d                   	pop    %rbp
  4011b2:	c3                   	retq 

从上面写的几个简单函数分析,rsp的值都不会变,所以函数退出,只需要把rbp还原,然后在ret到下一条执行的指令。

5.7 扩展:ARM平台汇编分析

本来不打算介绍ARM,因为我们以后都是使用Intel了,ARM芯片做服务器还是比较少的,但是想想如果作为比较的方式学习还是不错的。

5.7.1 RISC与CISC的区别

RISC全称Reduced Instruction Set Compute,精简指令集计算机。

CISC全称Complex Instruction Set Computers,复杂指令集计算机。

RISC的主要特点:
1)选取使用频率较高的一些简单指令以及一些很有用但不复杂的指令,让复杂指令的功能由使用频率高的简单指令的组合来实现。

2)指令长度固定,指令格式种类少,寻址方式种类少。

3)只有取数/存数指令访问存储器,其余指令的操作都在寄存器内完成。

4)CPU中有多个通用寄存器(比CICS的多)

5)采用流水线技术(RISC一定采用流水线),大部分指令在一个时钟周期内完成。采用超标量超流水线技术,可使每条指令的平均时间小于一个时钟周期。

6)控制器采用组合逻辑控制,不用微程序控制。

7)采用优化的编译程序

CICS的主要特点:
1)指令系统复杂庞大,指令数目一般多达200~300条。

2)指令长度不固定,指令格式种类多,寻址方式种类多。

3)可以访存的指令不受限制(RISC只有取数/存数指令访问存储器)

4)各种指令执行时间相差很大,大多数指令需多个时钟周期才能完成。

5)控制器大多数采用微程序控制。

6)难以用优化编译生成高效的目标代码程序

RISC与CICS的比较
1.RISC比CICS更能提高计算机运算速度;RISC寄存器多,就可以减少访存次数,指令数和寻址方式少,因此指令译码较快。

2.RISC比CISC更便于设计,可降低成本,提高可靠性。

3.RISC能有效支持高级语言程序。

4.CICS的指令系统比较丰富,有专用指令来完成特定的功能,因此处理特殊任务效率高。
这个转载自https://blog.csdn.net/weixin_40491661/article/details/121351113

很明显,Intel的芯片就是CICS指令,现在我们来学习的ARM其实就是RISC,经常使用在手机芯片上,因为功耗较低。

5.7.2 ARM寄存器解读

在这里插入图片描述

ARM的寄存器比较多,但是指令是精简的,所以才是RISC的风格。

ARM的寄存器都是通用的都是R0-R15,但是因为ARM有7种模式,所以不同模式的寄存器用法可能不一样,主要是这些模式的寄存器虽然名字相同,但都是不同寄存器。

但是这里有两个寄存器比较特殊:

R13:栈顶指针

R15:指令寄存器

5.7.3 ARM对数据的描述

ARM的发展比较晚,基本没有经历过8位和16位的时代,所以ARM没有什么历史负担,所以对数据描述没有windows看的那么难受。

c声明Intel数据类型
char字节
short半字
int
long双字

5.7.4 ARM汇编浅析

这个就不写了,因为我现在没有交叉编译工具链,等后面有机会再说吧。

参考文章:

https://blog.csdn.net/weixin_39946798/article/details/113708680

https://blog.csdn.net/c2682736/article/details/122349851

https://blog.csdn.net/weixin_38633659/article/details/125221443

https://blog.csdn.net/oqqHuTu12345678/article/details/125676002

https://www.jianshu.com/p/338d2f85e954

https://blog.csdn.net/weixin_40491661/article/details/121351113

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

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

相关文章

【003hive基础】hive的数据类型

文章目录 一.数据类型1. 基础数据类型2. 复杂数据类型 二. 显式转换与隐式转换三. hive的读时模式 一.数据类型 1. 基础数据类型 2. 复杂数据类型 array: 有序相同数据类型的集合。 arrays(1, 2)map : key必须是基本数据类型&#xff0c;value不限。 map(‘a’, 1, ‘b’, 2)s…

线性回归、正规方程和梯度下降法

一、线性回归简介 1.定义与公式 线性回归是利用回归方程(函数)对一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。 特点&#xff1a;只有一个自变量的情况称为单变量回归&#xff0c;多余一个自变量情况的叫做多元回归 通用公式&#xff1a; y …

javascript基础三:谈谈 JavaScript 中的类型转换机制

一、概述 JS中有六种简单数据类型&#xff1a;undefined、null、boolean、string、number、symbol&#xff0c;以及引用类型&#xff1a;object 但是我们在声明的时候只有一种数据类型&#xff0c;只有到运行期间才会确定当前类型 let name y?allen:1上面代码中&#xff0c…

2023年NOC大赛创客智慧编程赛项Python 复赛模拟题(二)

题目来自:NOC 大赛创客智慧编程赛项Python 复赛模拟题(二) NOC大赛创客智慧编程赛项Python 复赛模拟题(二) 第一题: 编写一个成绩评价系统,当输入语文、数学和英语三门课程成绩时,输出三门课程总成绩及其等级。 (1)程序提示用户输入三个数字,数字分别表示语文、数学、…

ChatGPT-4 镜像网站推荐

文章目录 1. TomChat2. Ai Doge3. 二狗问答4. 小莓用AI5. Ora6. 未知名字7. VIVI-AI8. ATALK9. ChatGPT Web10 AIchatOS 什么是ChatGPT? ChatGPT&#xff0c;全称&#xff1a;聊天生成预训练转换器&#xff08;英语&#xff1a;Chat Generative Pre-trained Transformer&#…

抓取领域相关论文及摘要

抓取规划问题是指确定物体与手指间的一系列接触位置&#xff0c;使得手指能抵抗任意外力且灵活操作物体的能力。传统的基于分析的抓取规划需要根据已知的被抓物体模型根据力闭合的条件判断抓取的好&#xff0c;这种方法只适合对已知的物体进行抓取。 然而日常生活中有很多相似…

MyBatis 中的动态 SQL 是什么?它的作用是什么?

MyBatis 中的动态 SQL 是一种允许在 SQL 语句中根据不同的条件动态生成 SQL 语句的技术。它可以根据不同的条件生成不同的 SQL 语句&#xff0c;从而达到灵活构建 SQL 语句的目的。动态 SQL 可以减少代码的重复度&#xff0c;提高代码的可维护性和可读性。 动态 SQL 使用 OGNL…

如何在MyBatis中处理复杂结果集映射关系

文章目录 前言一、 准备工作二、resultMap处理字段和属性的映射关系三、多对一映射0、级联方式处理映射关系1、使用association处理映射关系2、分步查询解决多对一关系(1) 查询员工信息(2) 根据员工所对应的部门id查询部门信息延迟加载 三、一对多的关系处理0、使用collection来…

6.1 SpringBoot解决跨域,我推荐这2种超实用方案

文章目录 前言一、验证跨域1. 添加index.html2. 增加/auth/test/cors接口3. IDEA启动多个springboot项目4. 验证POST方法5. 验证OPTIONS方法 二、拦截器方案定义拦截器注册拦截器&#xff0c;并指定拦截规则 三、过滤器方案总结最后 前言 在文章【2-2】中&#xff0c;我和你介…

获取企业服务超市企业信息

地址&#xff1a; 服务机构-苏州工业园区企业服务超市 import os from datetime import datetime from urllib import request import pandas as pd import re import requests from lxml import etree from bs4 import BeautifulSoup import csv import codecs# 20230521 根据…

learn_C_deep_13 (深刻理解宏定义)

目录 宏定义 数值宏常量 字符串宏常量 用定义充当注释符号宏 用 define 宏定义表达式 宏定义中的空格 宏定义 数值宏常量 在C语言中&#xff0c;宏定义可以用于定义数值宏常量。数值宏常量是一个值&#xff0c;在宏定义中用一个常量名称来表示&#xff0c;该值在后续的代…

计算机视觉的应用5-利用PCA降维方法实现简易人脸识别模型

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用5-利用PCA降维方法实现简易人脸识别模型&#xff0c;本文将介绍如何使用主成分分析&#xff08;PCA&#xff09;实现简易的人脸识别模型。首先&#xff0c;我们将简要介绍PCA的原理及其在人脸识别中…

26 KVM热迁移虚拟机

文章目录 26 KVM热迁移虚拟机26.1 总体介绍26.1.1 概述26.1.2 应用场景26.1.3 注意事项和约束限制 26.2 热迁移操作26.2.1 前提条件26.2.2 热迁移脏页率预测&#xff08;可选&#xff09;26.2.3 设置热迁移参数&#xff08;可选&#xff09;26.2.4 热迁移操作&#xff08;共享存…

Linux:文本三剑客之awk

Linux&#xff1a;文本三剑客之awk 一、awk编辑器1.1 awk概述1.2 awk工作原理1.3 awk与sed的区别 二、awk的应用2.1 命令格式2.2 awk常见的内建变量&#xff08;可直接用&#xff09; 三、awk使用3.1 按行输出文本3.2 按字段输出文本3.3 通过管道、双引号调用 Shell 命令 一、a…

【模电实验】日光灯电路及功率因数的提高

实验4 日光灯电路及功率因数的提高 一、实验目的 1&#xff0e;理解提高功率因数的意义并掌握其方法。 2&#xff0e;掌握日光灯电路的联接。 二、原理说明 日光灯电路结构及工作原理 日光灯电路如图4-1所示&#xff0c;日光灯由灯管、镇流器和启辉器三部分组成。 &…

复制带随机指针的链表

&#x1f495;“如果你关注自己已经拥有的&#xff0c;你就会拥有更多。如果你只关注自己没有得到的&#xff0c;你永远不会满足。” - 奥普拉温弗瑞&#x1f495; &#x1f43c;作者&#xff1a;不能再留遗憾了&#x1f43c; &#x1f386;专栏&#xff1a;Java学习&#x1f3…

11. Redis集群(cluster)

11. Redis集群cluster 是什么&#xff1f;能干嘛&#xff1f;集群算法-分片-槽位slot官网出处redis集群的槽位slotredis集群的分片他两的优势slot槽位映射&#xff0c;一般业界有3种解决方案哈希取余分区—致性哈希算法分区3大步骤算法构建一致性哈希环redis服务器IP节点映射k…

【Python sqlite3】零基础也能轻松掌握的学习路线与参考资料

Python sqlite3是Python语言自带的轻量级关系数据库管理系统&#xff0c;它可以让我们在不需要额外的安装和配置下&#xff0c;使用SQLite数据库进行操作和管理。SQLite是一个功能强大的嵌入式数据库&#xff0c;它非常适合在轻量级应用程序中使用&#xff0c;如桌面应用程序、…

ROS学习(4)——launch文件的编写

对于一个复杂的系统,会有十几个、几十个甚至是上百个节点在运行,如果我们每次都是采取“打 开终端、运行 rosrun 指令”来启动应用程序,显得效率非常低。我们需要一个更方便的方式来启动系统。ROS 中提供了“使用 launch 文件 roslaunch”命令来完成系统的启动。具体的实现方法…

Java·Lambda

文章目录 ⚽️1 背景⚽️&#x1f34f;1.1 Lambda表达式的语法&#x1f34f;&#x1f34e;1.2 函数式接口&#x1f34e; &#x1f3c0;2 Lambda表达式的基本使用&#x1f3c0;&#x1f348;2.1 语法精简&#x1f348; &#x1f3c8;3 变量捕获&#x1f3c8;&#x1f3c6;3.1 匿…