由浅入深学习 C 语言:Hello World【提高篇】

news2024/9/21 10:56:10

目录

 

引言

1. Hello World 程序代码

2. C 语言角度分析 Hello World 程序

2.1. 程序功能分析

2.2 指针

2.3 常量指针

2.4 指针常量

3. 反汇编角度分析 Hello World 程序

3.1 栈

3.2 函数用栈传递参数

3.3 函数调用栈

3.4 函数栈帧

3.5 相关寄存器

3.6 相关汇编指令

3.7 汇编代码分析

3.7.1 invoke_main() 函数调用了 main 函数

3.7.2 main 函数的栈帧建立和销毁过程

3.7.3 "Hello World\n" 字符串在内存中是以 assii 码的形式保存


 

引言

        本篇是 Hello World 程序提高篇,默认读者是有 C 语言编程基础,0 基础建议先阅读这篇博文的姐妹篇之由浅入深学习 C 语言:Hello World【基础篇】-CSDN博客

1. Hello World 程序代码

#include <stdio.h>   

int main(int argc, const char *argv[])
{
	for (int i = 0; i < argc; i++)    // 打印命令行参数
		printf("argv[%d] = %s\n", i, argv[i]);

	printf("Hello World\n");	

	return 0;        
}

c10cabd517a34496915f8ab949fe1555.png

2. C 语言角度分析 Hello World 程序

2.1. 程序功能分析

1.  #include <stdio.h>   // 预处理器指令,用于把 stdio.h 文件包含进来

2. int main(int argc, const char *argv[])        // 主函数,是 C 程序的入口函数

(1)函数名前的 int 是函数的返回值

(2)argc 是函数的第一个参数,参数类型是 int

  • 该参数表示的是命令行参数个数,传给这个参数的值是第二个参数 argv 数组的元素个数。
  • 一个 C 程序至少有一个命令行参数,这个命令行参数是 可执行文件的绝对路径名或相对路径名

(3)argv 是函数的第二个参数,参数类型是 const char* 数组,也就是说这个参数是一个数组,数组的每一项存的数据类型是 const char* (常量字符指针),指向每一个命令行参数字符串的首地址。

比如,我们的 Hello World 程序,在 linux 平台下,生成 main.out 可执行文件。 

  • ./main.out  // 不带参数运行程序
  • argc = 1
  • argv 数组有 1 个元素,这个元素存储的就是 "./main.out" 这个字符串在内存中的首地址

343c4198edfd48f680245ec55563351b.png

  • ./main.out 111 222 // 带 2 个参数运行程序
  • argc = 3
  • argv 数组有 3 个元素,第一个元素存储的是 "./main.out" 这个字符串在内存中的首地址,第二个元素存储的是 "111" 这个字符串在内存的首地址,第三个元素存储的是 "222" 这个字符串在内存的首地址 

ee1d32a9355549d18c9ec25e22adc2de.png

2.2 指针

        在 C 语言中,不管什么数据类型的指针,实际就是一块大小固定的内存,这块内存存的值是另一块内存单元的地址,也就是说这块内存存在的意义是为了指向另一块内存单元,所以我们把它称作指针。

  • 32 位程序中,这块内存大小是 4 个字节
  • 64 位程序中,这块内存大小是 8 个字节       
#include <stdio.h>

int main(int argc, const char* argv[]) {
	int* pi = NULL;			// int* 指针
	double* pd = NULL;		// double* 指针 

	printf("pi size = %d\n", sizeof(pi));	// 32位程序,pi size = 4; 64位程序,pi size = 8
	printf("pd size = %d\n", sizeof(pd));	// 32位程序,pd size = 4; 64位程序, pd size = 8

	return 0;
}

e8d9eea74b1a413f95a4f3f2c655816d.png

7ac597045f884f1db7ecd8533dbf0b8e.png

2.3 常量指针

        常量指针,本质是一个指针,用 const 修饰的指针是常量指针,也就是说这个指针指向另一块存储常量的内存单元,所以我们不能通过常量指针来修改它指向的另一块内存单元的值。

#include <stdio.h>

int main(int argc, const char* argv[]) {
	for (int i = 0; i < argc; i++) {
		// *argv[i] = '1';	// error: 常量指针,这个指针指向的内存单元的值是常量,不能被修改
		printf("argv[%d] = %s\n", i, argv[i]);
	}
		

	return 0;
}

        所以,main 函数的第二个参数声明为 const char* (常量字符指针) 可以防止我们在实际的开发中,不小心修改了 argv 数组里的元素指向的内存单元的值。

2.4 指针常量

        指针常量,本质是一个常量,用指针类型修饰的常量是指针常量,常量就必须声明的时候初始化,初始化后整个程序运行期间都不能再修改。

#include <stdio.h>

int main(int argc, const char* argv[]) {
	int a = 1;
	int* const pi = &a;
	int b = 2;
	// pi = &b;		// error: 常量不能被修改

	return 0;
}

3. 反汇编角度分析 Hello World 程序

3.1 栈

        在计算机科学中,栈是一种后进先出(LIFO,last in first out)的数据结构,往栈中写数据,叫做入栈(push),将栈顶数据从栈中弹出,叫做出栈(pop)。

3.2 函数用栈传递参数

        函数用栈传递参数的原理是由调用者通过 push 指令将需要传递给被调用者的参数压入栈中,被调用者从栈中取得参数。

3.3 函数调用栈

        C 语言,所有函数的调用,是通过栈来实现的,当函数被调用时,其返回地址、参数以及局部变量会被压入栈中。当函数返回时,这些信息会从栈中弹出,以恢复到函数被调用之前的状态。我们可以称维护所有函数调用形成的这个栈空间为函数调用栈。

        特别要注意的是,一个 C 程序的栈是往下生长的,即栈底在高地址,栈顶在低地址,入栈,栈顶地址会变小,出栈,栈顶地址会变大。

3.4 函数栈帧

        在 C 语言中,从一个函数的进入,到这个函数返回,形成的栈空间,我们称为函数栈帧。函数栈帧不是固定不变的,而是随着函数的功能,栈帧空间可能随时在变大或缩小。

3.5 相关寄存器

  • ebp: extended base pointer,扩展基址指针寄存器,一般用于当进入一个函数时,存放该函数的栈帧基址(栈帧的栈底)。
  • esp: extended stack pointer,扩展栈指针寄存器,存放函数栈的栈顶
  • ebp 和 esp 配合使用,共同协作维护着正在运行的函数的栈帧
  • ebx: extended base register,扩展基址寄存器,特别是在数组和字符串操作中,它可以用来存储数组或字符串的基地址。此外,在调用操作系统函数时,ebx 有时也用于传递参数。
  • esi: extended source index register,扩展源索引寄存器,在字符串操作中非常有用。它通常用于存储源字符串或数据数组的起始地址,在字符串指令(如 stos 等)中自动递增,以便按顺序处理数据。
  • edi: extended destination index register,扩展目的索引寄存器,与 esi 相对应,在字符串操作中用于存储目标字符串或数据数组的起始地址。与 esi 相似,edi 也在字符串指令中自动递增,以接收来自源地址的数据。
  • ecx: extended count register,扩展计数寄存器,在循环操作中尤其重要。它存储了循环的迭代次数,很多指令(如以 rep 为前缀的字符串操作指令)都会递减 ecx 的值来控制循环次数。
  • eax: extended accumulator register,eax 寄存器通常用作累加器,在算术和逻辑运算中扮演主要角色。它经常用于存储操作数、结果以及中间值。在函数调用中,eax 常用于存放函数的返回值。

3.6 相关汇编指令

  • mov: 数据传送指令,比如:mov eax, 3        ;将 3 这个立即数传送给 eax 寄存器
  • push: 往栈中压入数据,CUP操作原理:(1) esp - 存储压入栈数据占用的字节数。 (2) 修改栈顶数据为要压入栈中的数据。比如:push eax        ;将 eax 寄存器的数据压入栈中
  • pop: 从栈顶弹出数据, CUP操作原理:(1) 栈顶数据弹出栈  (2) esp + 从栈顶弹出的数据占用的字节数。比如:pop eax        ;将栈顶数据弹出,送到 eax 寄存器中
  • sub: 减法指令
  • add: 加法指令
  • rep: 在汇编语言中,rep 的作用是根据 ecx 的值,循环执行跟在其后的指令,直到 ecx = 0 时为止。
  • cmp: 根据操作结果,设置标志寄存器相关位的值,其他相关指令就可以拿到寄存器相关位的值进行相关操作
  • stos: 字符串操作指令,比如:stos dword ptr es:[edi],是将 eax 的值传送给 es:[edi] 为首地址的 4 个字节单元中
  • xor: 二进制异或操作,即 1 xor 0 = 1
  • call: 转到标号处执行指令
  • ret: 跟 call 配合,控制 cpu 返回到调用该函数的指令的下一条指令执行

3.7 汇编代码分析

int main(int argc, const char* argv[]) {
00E517E0  push        ebp       ;调用者的栈帧基址入栈
00E517E1  mov         ebp,esp   ;构建自己的栈帧基址
00E517E3  sub         esp,0C0h  ;栈顶往上移 0C0h 字节
00E517E9  push        ebx       ;调用者的 ebx 入栈,esp = esp-4
00E517EA  push        esi       ;调用者的 esi 入栈,esp = esp-4
00E517EB  push        edi       ;调用者的 edi 入栈,esp = esp-4
00E517EC  mov         edi,ebp   ;edi = ebp
00E517EE  xor         ecx,ecx   ;等价于 mov ecx, 0; 但 xor 指令更高效
00E517F0  mov         eax,0CCCCCCCCh  
00E517F5  rep stos    dword ptr es:[edi]    ;rep 的作用是根据 cx 的值,循环执行跟在其后的指令,直到 cx = 0 时为止。
                                            ;stos 串传送指令,相当于 mov es:[di], eax 
                                            ;df = 0, edi = edi + 4; df = 1, edi = edi - 4;
    printf("Hello World\n");
00E517F7  push        offset string "Hello World\n" (0E57B30h)  ;传递给 printf 函数的参数入栈,esp = esp-4
                                                                ;offset:取得字符串 "Hello World\n" 在内存的首地址
00E517FC  call        _printf (0E510CDh)    ;调用 printf 函数 
00E51801  add         esp,4     ;esp = esp+4,即销毁传递给 printf 函数的参数 "Hello World\n" 在内存的首地址占用的空间
    return 0;
00E51804  xor         eax,eax   ;函数返回值放在 eax 寄存器,该指令相当于 mov eax, 0; 但 xor 指令更高效
}
00E51806  pop         edi       ;还原调用者的 edi,esp = esp+4
00E51807  pop         esi       ;还原调用者的 esi,esp = esp+4
00E51808  pop         ebx       ;还原调用者的 ebx,esp = esp+4
00E51809  add         esp,0C0h  ;栈顶往下移 0C0h 字节
00E5180F  cmp         ebp,esp   ;计算 ebp-esp,然后根据结果对 cpu 的标志寄存器进行设置
                                ;目的是让下一条指令 call __RTC_CheckEsp,检测该函数栈帧建立前跟销毁该函数栈帧后的esp 是否一致
00E51811  call        __RTC_CheckEsp (0E5123Fh)  ;检测标志寄存器相关位的值,从而判断 ebp 跟 esp 是否相等
00E51816  mov         esp,ebp   ;恢复调用者的栈顶
00E51818  pop         ebp       ;恢复调用者的栈顶基址
00E51819  ret                   ;调用者通过 call 指令调用函数会 push eip, 自己必须 ret 指令,pop eip

3.7.1 invoke_main() 函数调用了 main 函数

        查看 VS2019 的调用堆栈窗口,我们可以知道,框架的 invoke_main() 函数调用了我们

main 函数。

2ec43735487547f08e4924959ed146a4.png

3.7.2 main 函数的栈帧建立和销毁过程

(1)进入主函数栈的初始状态

f03d9aaa5fd64cb5b5c96841f35ff0d0.png4fa2193bbcc947cb983cd639cf627966.png

(2)cpu 执行 push ebp

33b196a5785743b39944a5a050b6476c.png12ba992e03c84b85912a26af42625d09.png

(3)cpu 执行 mov ebp, esp

5cf00ce3a3254d288c604d30ee9f61cc.png

a86a4aa44cca4d18a847190d53ba18af.png

(4)cpu 执行 sub esp, 0C0h

f99fe424b97c46aeb8d040337084e5ba.pngd913a8d5b15a4b9c98cc7b8100ec743d.png

(5)cpu 执行 push ebx

ed128ef92ec3417da700a12c4757ca65.png

d32d4afeb10c4c10b9313079269d1f9a.png

(6)cpu 执行 push esi

eb96338c34d14b1f8cf0cb3cc7d86aad.png

8898c819c8b241cd85d4b2eebdf19af4.png

(7)cpu 执行 push edi

b43f8a6d84614b4a9211954bfad86a64.png

86fc1c6d2c994abfb138fc09e495207f.png

(8)cpu 执行以下指令对栈没影响

00E517EC  mov         edi,ebp   ;edi = ebp
00E517EE  xor         ecx,ecx   ;等价于 mov ecx, 0; 但 xor 指令更高效
00E517F0  mov         eax,0CCCCCCCCh  
00E517F5  rep stos    dword ptr es:[edi]    ;rep 的作用是根据 cx 的值,循环执行跟在其后的指令,直到 cx = 0 时为止。
                                            ;stos 串传送指令,相当于 mov es:[di], eax 
                                            ;df = 0, edi = edi + 4; df = 1, edi = edi - 4;

(9)cpu 执行 push offset string "Hello World\n"

627fcb502d2f471fad92f669ddec32d8.png

164f4a73b71c4f49b067414b47cb6181.png

(10)cpu 执行 call _printf 指令,cpu 进入 _printf 函数执行完该函数包含的指令返回后,栈又回到了跟没执行 _printf 函数一样的状态。

(11)cup 执行 add esp, 4

84a94f0d64f648beb5a4ff2ce7553c56.png

519c1483a3504b7884a8038c213f9251.png

(12)cpu 执行 xor eax, eax 对栈没影响

(13)cpu 执行 pop edi

        892bc76573ff41e982ac102dde6817bc.png

ed74f3a335804e8cb054d6765967d030.png

(14)cpu 执行 pop esi

7430c7a140da4185a04ceeaf504177e6.png

13c4cedd5fb143369971dfbd9d2822b8.png

(15)cpu 执行 pop ebx

ffd41c4548b643df945173d2e998fcf9.png

b2de40e4933543a394a61e1b091dfa0e.png

(16)cpu 执行 add esp, 0C0h

4ea74ab0d8734eba8250b0855414f11f.png

ab296b41a7b5441bb612ac48c093b13f.png

(17)cpu 执行以下指令对栈没影响

00E5180F  cmp         ebp,esp   ;计算 ebp-esp,然后根据结果对 cpu 的标志寄存器进行设置
                                ;目的是让下一条指令 call __RTC_CheckEsp,检测该函数栈帧建立前跟销毁该函数栈帧后的esp 是否一致
00E51811  call        __RTC_CheckEsp (0E5123Fh)  ;检测标志寄存器相关位的值,从而判断 ebp 跟 esp 是否相等

(18)cpu 执行 mov esp, ebp

4d39a3eab99e4c5f85511775f00132eb.png

6c1367cb3bb84796aee351e84cc933be.png

(19)cpu 执行 pop ebp

67734e7a8f674623ba0e989156052b98.png

94705d53d3894a5ba2d941e34b3c221e.png

此时,我们看到函数栈的状态回到了跟刚进入该函数时的初始状态是一致的。

3.7.3 "Hello World\n" 字符串在内存中是以 assii 码的形式保存

        fdc1ff0925384ae09fc325091a05e8f1.png

大写字母 H:assii 码为 72,十六进制表示为 0x48

小写字母 e:assii 码为 101,十六进制表示为 0x65

小写字母 l:assii 码为 108,十六进制表示为 0x6c

小写字母 o:assii 码为 111,十六进制表示为 0x6f

大写字母 W:assii 码为 87,十六进制表示为 0x57

小写字母 r:assii 码为 114,十六进制表示为 0x72

小写字母 d:assii 码为 100,十六进制表示为 0x64

换行符 \n:assii 码为 10,十六进制表示为 0x0a

 

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

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

相关文章

离散傅里叶变换(Discrete Fourier Transform, DFT)介绍,地震波分析

介绍 离散傅里叶变换&#xff08;Discrete Fourier Transform, DFT&#xff09;是一种非常重要的信号处理工具&#xff0c;它将离散时间信号从时间域转换到频率域。DFT在信号处理、图像处理、通信系统以及许多其他工程和科学领域中得到了广泛应用。为了理解DFT&#xff0c;我们…

时序预测 | 基于DLinear+PatchTST多变量时间序列预测模型(pytorch)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 DLinearPatchTST多变量时间序列 dlinear,patchtst python代码&#xff0c;pytorch架构 适合功率预测&#xff0c;风电光伏预测&#xff0c;负荷预测&#xff0c;流量预测&#xff0c;浓度预测&#xff0c;机械领域预…

3.美食推荐系统(Java项目springboot和vue)

目录 0.系统的受众说明 1 绪论 1.1研究背景 1.2研究现状 1.3研究内容 2 系统关键技术 2.1 Springboot框架 2.2 JAVA技术 2.3 MYSQL数据库 2.4 B/S结构 3 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2经济可行性 3.1.3操作可行性 3.2 系统性能分析 3.3 系统功能分析 3.4系统…

【3D目标检测】MMdetection3d——nuScenes数据集训练BEVFusion

引言 MMdetection3d&#xff1a;【3D目标检测】环境搭建&#xff08;OpenPCDet、MMdetection3d&#xff09; MMdetection3d源码地址&#xff1a;https://github.com/open-mmlab/mmdetection3d/tree/main?tabreadme-ov-file IS-Fusion源码地址&#xff1a;https://github.co…

139. MySQL同步ES的四种方案

文章目录 1. 前言2. 数据同步方案2.1 同步双写2.2 异步双写2.3 基于 SQL 抽取2.4 基于 Binlog 实时同步 3. 数据迁移工具选型3.1 Canel3.2 阿里云 DTS3.3 Databus3.4 其它 4. 后记 本文介绍数据同步的 4 种方案&#xff0c;并给出常用数据迁移工具&#xff0c;目录如下&#xf…

【软件测试专栏】认识软件测试、测试与开发的区别

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;软件测试专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 认识软件测试、测试与开发的区别 关键词&#xff1a;软件测试、测…

最短路算法详解(Dijkstra 算法,Bellman-Ford 算法,Floyd-Warshall 算法)

文章目录 一、Dijkstra 算法二、Bellman-Ford 算法三、Floyd-Warshall 算法 由于文章篇幅有限&#xff0c;下面都只给出算法对应的部分代码&#xff0c;需要全部代码调试参考的请点击&#xff1a; 图的源码 最短路径问题&#xff1a;从在带权图的某一顶点出发&#xff0c;找出…

【PyCharm激活码】2024年最新pycharm专业版激活码+安装教程!

一、PyCharm激活 激活码&#xff1a; KQ8KMJ77TY-eyJsaWNlbnNlSWQiOiJLUThLTUo3N1RZIiwibGljZW5zZWVOYW1lIjoiVW5pdmVyc2l0YXMgTmVnZXJpIE1hbGFuZyIsImxpY2Vuc2VlVHlwZSI6IkNMQVNTUk9PTSIsImFzc2lnbmVlTmFtZSI6IkpldOWFqOWutuahtiDorqTlh4blupflkI0iLCJhc3NpZ25lZUVtYWlsIjoi…

ArcEngine二次开发实用函数18:使用shp矢量对栅格文件进行掩模和GP授权获取

目录 1. 权限设置 2. 添加如下引用 3. 核心代码: 首先要确定要使用的gp工具需要什么权限,这个可以在工具的帮助中查看;获取权限之后,引用名称空间,编写处理代码: 下面给出具体的实例代码: 1. 权限设置 ESRI.ArcGIS.RuntimeManager.Bind(ESRI.ArcGIS.ProductCode.Eng…

介绍一下最近很火的一款游戏黑神话悟空,以及国产游戏面临的挑战

《黑神话&#xff1a;悟空》是一款由杭州游科互动科技有限公司开发的单机动作角色扮演游戏&#xff0c;以中国古典名著《西游记》为背景。游戏在2024年8月20日上线&#xff0c;支持PC&#xff08;Steam、Epic、Wegame&#xff09;和PlayStation 5平台&#xff0c;未来还将登陆X…

OpenCV绘图函数(13)绘制多边形函数函数polylines()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 画几条多边形曲线 函数原型 void cv::polylines (InputOutputArray img,InputArrayOfArrays pts,bool isClosed,const Scalar & color…

浅谈 Android 15 新 API:确保 TextView 完整展示、不被切断~

本文为稀土掘金技术社区首发签约文章&#xff0c;30天内禁止转载&#xff0c;30天后未获授权禁止转载&#xff0c;侵权必究&#xff01; 前言 很多语言和文字拥有特殊的、复杂的写法、画法&#xff0c;一个字符可能延伸到前一个字符的区域&#xff0c;甚至后一个字符的区域。 …

力扣375.猜数字大小 II

力扣375.猜数字大小 II dp dp[i][j]是说依次以从i到j的数字作为分割点(猜的数)&#xff0c;必定赢的游戏所用钱的最小值。 枚举每一列&#xff0c;从下往上算出dp[i][j]&#xff0c;最终答案为dp[1][n] class Solution {public:int getMoneyAmount(int n) {if(n 1)retu…

巧用scss实现一个通用的媒介查询代码

巧用scss实现一个通用的媒介查询代码 效果展示 实现代码 <template><div class"page-root"><div class"header"></div><div class"content"><div class"car-item" v-for"item in 9">…

20行为型设计模式——访问者模式

一、访问者模介绍 访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为型设计模式&#xff0c;用于将操作封装在访问者对象中&#xff0c;以便在不改变被访问对象的类的前提下&#xff0c;定义新的操作。它允许你在不修改现有代码的情况下&#xff0c;向对象结构中添…

类和对象以及内存管理

对象拷贝时的编译器优化 现代编译器会为了尽可能提高程序的效率&#xff0c;在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷贝。如何优化C标准并没有严格规定&#xff0c;各个编译器会根据情况自行处理。当前主流的相对新⼀点的编译器对于连续⼀个…

电池信息 v5.29.11 高级版,智能优化充电,最多可延长50%电池寿命

Charging Master 是一款非常实用的安卓 APP&#xff0c;专注于为您的手机充电提供最佳体验。借助其智能优化功能&#xff0c;Charging Master 能够最大程度地延长电池寿命&#xff0c;最多可达 50% 的节省。此外&#xff0c;该应用还提供了一系列功能&#xff0c;助您更好地管理…

提升团队效率的9款免费办公工具评测

本文主要介绍了以下9款协同办公软件&#xff1a;1.Worktile&#xff1b;2.PingCode&#xff1b;3.石墨文档&#xff1b;4.Teambition&#xff1b;5.蓝湖&#xff1b;6.工作宝&#xff1b;7.飞书&#xff1b;8.Asana&#xff1b;9.ClickUp。 在现代职场中&#xff0c;团队协作已…

GD - GD32350R_EVAL - PWM实验和验证1

文章目录 GD - GD32350R_EVAL - PWM实验和验证1概述笔记实验设计实验环境GD32350R_EVAL 的硬件连接修改程序配置 - 只产生PWM波&#xff0c;不要CMP清除波形TIMER0时钟设置TIMER0的PWM设置参数设置main()中PWM波形的开启代码示波器测量结果如果要产生4KHZ的PWM需要设置怎样的参…

在centos系统中kill掉指定进程

如上图&#xff0c;我想kill掉 python3 func_tg_1_vps.py这个进程&#xff08;而不kill掉python3 func_tg_2_vps.py&#xff09;。 解决方法&#xff1a; 第一步&#xff1a;首先使用ps -ef | grep python3命令&#xff0c;查出所有包含python3的命令 拿其中一条讲解 root …