使用gcc展示完整的编译过程(gcc预处理模式、编译模式、汇编模式、连接模式)

news2024/11/22 21:53:58

最近在了解 clang/llvm 的时候突然发现一件事:gcc是一个工具集合,包含了或者调用将程序源代码转换成可执行程序文件的所有工具,而不只是简单的编译器。这帮助我对“编译器”有了更深刻的理解,所以写下本文作为记录。

关于“编译器”的原理和机制的更详细内容在另一篇文章里:clang到底是什么?是编译器?gcc和clang有什么区别?(严格说是gcc和llvm/clang的区别)

从源代码转换成可执行程序的完整过程

从源代码转换成可执行程序的完整过程,也就是我们平时所说的“编译过程”,实际如下(圆角矩形表示代码,矩形表示各种处理器):

源代码
预处理器
调整之后的代码
编译器
汇编语言代码
汇编器
可调整的机器语言代码
连接器或加载器
可执行程序

可以看到从源代码到可执行程序要经过预处理器(preprocessor)、编译器(compiler)、汇编器(assembler)和连接器(linker)或加载器(loader),而编译器只是负责将源代码转换成对应的汇编代码的功能。

接下来,就用gcc来展示上图中的流程。

gcc和配套的cpp、as、ld处理转换程序

上面的几种处理转换程序除了编译器和汇编器,其他三个估计都很很少听到。下面就用最经典的 C 语言和gcc来介绍这个过程,gcc包含的预处理器为cpp,还会调用汇编器as、连接器ld

我之前以为asld也是包含在gcc中的。但是 GNU 开发组的人告诉我,gcc只包含预处理器和编译器,汇编器和连接器不是 gcc 项目的一部分,而且必须单独提供(很多系统中,是 binutils 项目提供的)(经过验证,Ubuntu 就是这样)

三者的手册界面如下:

请添加图片描述

再次说明一下,虽然gcc这个命令叫“编译器”,但是包含了预处理器,以及会自动调用了其他的处理程序,所以在表面上看,是gcc自己完成源代码到可执行程序转换。

由于是多个工具组成的,所以你也可以自己决定进行到哪一步,下面你就能看到了。

编译过程演示准备

为了方便读者理解编译过程,将会使用一个实例程序来进行演示。示例程序将会计算12*13*14,并且如果值小于1000就输出值,否则提醒超出了(也就是必定体系)。

本文使用

下面列一下准备工作,一共三个文件和一个空白目录build

空白目录build是为了方便处理生成文件,不然两次测试可能会搞得乱糟糟。可以使用下面的语句生成:

mkdir build

三个文件为main.ccalc.ccalc.h ,包含的代码如下:

//main.c
#include <stdio.h>
#include "calc.h"

#define MAXNUM 1000

int main()
{
    int a = calc(12, 13, 14);
    
    if (a >= MAXNUM)
        printf("Over Limit!\n");
    else
        printf("Result: %d", a);
    
    return 0;
}
//calc.c
#include <stdio.h>
#include "calc.h"

int calc(int a, int b, int c)
{
	return a*b*c;
}
//calc.h
int calc(int, int, int);

由于gcc可以指定进行到哪一步,所以我们先用gcc进行演示,再使用cppgccasld进行演示。

使用gcc进行分步演示编译过程

gcc支持在将源代码转换成可执行程序的过程中的某一步停止,也就是限定终点(如果依次手动操作的话,也就是只进行某一步),我们就先用这个机制来展示整个编译过程。

使用gcc演示会简单一些,其次绝大部分程序的使用几乎等价gcc加选项,除了一些小细节。唯一大不同的是连接器。

所以本节只是为了让你了解流程,每个处理器的详细介绍和扩展见下一节。

预处理阶段

首先是预处理源代码,如果需要在预处理之后停止需要使用选项-E

$ gcc -E ../calc.c -o calc.i
$ gcc -E ../main.c -o main.i

这里需要使用-o来指定输出文件名称,这是因为gcc调用的预处理器cpp会将处理后的内容输出到标准输出,而不是生成某个文件。预处理之后的文件后缀为.i,所以这里把后缀都换成.i。此时使用ls查看如下:

$ ls
calc.i  main.i

编译阶段

接下来是编译阶段。如果你想编译但不进行汇编,那么使用-S选项:

$ gcc -S main.i calc.i

这里会对两个文件进行编译,并生成汇编语言文件,生成文件名是将原文件的.c.i等后缀替代成.s(因为是从某一步开始,到编译这步停止,不同后缀表示开始的阶段),此时使用ls查看如下:

$ ls 
calc.i  calc.s  main.i  main.s

汇编阶段

接下来是汇编阶段。这里使用选项-c,这个选项是进行编译和汇编,但是不进行连接。上文提到过,是根据后缀判断从整个编译流程的哪一步开始,然后进行到汇编之后、连接之前这个阶段。刚才是进行了编译,但是没有汇编,所以这里使用-c相当于只使用了汇编器:

$ gcc -c calc.s main.s

-S选项一样,它会自动生成对象文件(object file),文件名为用.o替代掉源文件名的.c.i.s等后缀,此时使用ls查看如下:

$ ls
calc.i  calc.o  calc.s  main.i  main.o  main.s

连接阶段

这是最后的连接阶段,使用-o直接输出即可,因为从汇编之后的对象文件到可执行程序和从源代码到可执行程序这个结果是一样的,gcc这些选项只是限定“终点”。这里我们将输出文件名设定为calc

$ gcc -o calc main.o calc.o

使用ls看到如下内容:

$ ls
calc  calc.i  calc.o  calc.s  main.i  main.o  main.s

这时候我们运行calc看看:

$ ./calc
Over Limit!

运行正常!

单独调用cppgccasld

这部分除了连接器,其他几乎和使用gcc加选项差不多,所以会说很多细节和扩展的内容。

这里是处于演示学习目的,所以从头到尾依次调用。实际上并不会这样使用,只会单独使用个别工具,或者某个阶段特地使用某个工具。

使用预处理器cpp

首先第一步是使用预处理器cpp

$ cpp ../calc.c -o calc.i
$ cpp ../main.c -o main.i
$ ls
calc.i main.i

直接cpp并不会产生文件,而是直接输出到标准输出了,如果你想保存成文件需要使用选项-o,这里我们必须保存,所以必须使用-o。但是由于这是一对一的,所以需要两条命令。

预处理器会将源代码中引用头文件(#include)、宏(#define)、状态控制(ifdef 等)等内容进行转换,以供后面的步骤使用。由于这一步主要是将宏进行替换,所以预处理器也被称为宏处理器(macro processor)。

这里我说的是供后面步骤使用,而不是供编译器使用,这是因为预处理器可能运行不止一次,或者做其他用途。你甚至可以在汇编代码中加入 C 语言的宏,然后使用预处理器将其处理好,再用汇编器转换成可执行程序(C headers in Asm)。

我们来看看预处理后的内容(main.i),可以看到产生了很多内容(wc统计为 743 行,源文件才 16 行),不过前面那一大堆的部分都是#include <stdio.h>的替换,主要看最后的这一部分:

请添加图片描述

可以看到#include "calc.h"被替换成了头文件中的int calc(int, int, int);,源代码中的宏MAXNUM也被替换成了对应的1000

使用编译器gcc

接下来要使用编译器将预处理过的文件编程,由于编译器就是gcc,所以我们还是要使用gcc -S来将预处理之后的文件转换成汇编文件:

$ gcc -S main.i calc.i
$ ls 
calc.i  calc.s  main.i  main.s

这时候生成的文件就是汇编语言:

	.file	"calc.c"
	.text
	.globl	calc
	.type	calc, @function
calc:
.LFB0:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	%edx, -12(%rbp)
	movl	-4(%rbp), %eax
	imull	-8(%rbp), %eax
	imull	-12(%rbp), %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	calc, .-calc
	.ident	"GCC: (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

需要注意一件事,如果你要拿这样生成汇编文件进行学习汇编,那么去看看我的一篇文章:【更新中】苹果自家的as汇编器的特色风格(与微软masm汇编器语言风格的不同),因为我知道很多人对汇编的学习是通过学校或者大学教材,而国内外很多教材都是微软的masm汇编器风格的汇编语言,这与as的语法不一样,有些地方是相反的。苹果虽然是自己写的as,但是与 GNU 的as只是稍有不同,很多语法几乎是一样的,所以可以参考一下

不过非常不建议使用gcc生成的汇编代码学习汇编,因为多余的东西太多了,后面的连接器ld部分就可以看出来引用了多少库。

使用汇编器as

接下来是将汇编文件转换成对象文件(也就是包含机器语言的文件),这需要as,它会像gcc -c一样自动替换后缀:

$ as calc.s -o calc.o
$ as main.s -o main.o
$ ls
a.out  calc.o calc.i  calc.s  main.i  main.o main.s

但是需要注意不能一次处理两个文件,不然会报错:

$ as calc.s main.s
main.s: Assembler messages:
main.s:12: Error: symbol `.LFB0' is already defined
main.s:45: Error: symbol `.LFE0' is already defined

这里是因为两个文件都有.LFB0.LFE0部分,重复了,所以要么单独编译二者,后面连接器会自动处理掉,要么进去手动删一下这部分,就可以生成为一个对象文件。

使用连接器ld

说真的这里只是展示才用ld,哪怕你是直接手写的汇编,也建议使用gcc来进行处理。因为现在 Ubuntu 的引用库非常复杂,ld默认可以访问的路径是空的,需要手动设置,文档和网上说的crt0.o的使用也不顺利。

连接器的工作

为了介绍连接器,这里需要先解释一下程序实际上是如何运行的,详细信息你可以自己看看一些专业书,这里只做简单不严谨的解释:程序本质是存放机器语言的文件,在打开之后会放进内存,不论是 swap 还是快表等任何内存存放机制,它在逻辑上是连续的,有一个程序计数器会记录当前的绝对内存地址,当内存地址到这个程序的空间内的时候,就开始调用这个程序的指令(机器语言)来进行操作。所以不论如何跳转、循环,它实际上都是连续、顺序的。

存放指令的文件就是对象文件,但是这时候可能顺序不对、不包括引用的外部库的指令,或者有其他的问题还不能直接执行。而将这些指令按照源代码设计、顺序摆放到一起,变成一个可以放到内存中的可执行文件,这就是连接器的工作。

此时如果用file查看此时的main.o文件,那么可以看到这是一个“relocatable(可重新分配的)”文件:

$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

如果你去运行它,是无法运行的,会显示“执行格式错误:

$ ./main.o
-bash: ./main.o: cannot execute binary file: Exec format error

连接器会将其和其他对象文件“组合”在一起,为每一部分分配好自己的相对地址,形成一个最终的可执行文件。这样加载入内存的时候,就可以形成绝对地址,然后就可以让程序计数器调用这些地址的指令了。

使用ld完成最后的工作

如果此时你用ld简单地连接两个对象文件,那么会出现以下错误:

$ ld main.o calc.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: main.o: in function `main':
main.c:(.text+0x37): undefined reference to `puts'
ld: main.c:(.text+0x52): undefined reference to `printf'

这里有两类错误,下面依次解释一下。
第一个错误ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000是因为 Ubuntu 程序的默认入口是_start,而不是经典的main,所以需要修改一下。
第二个错误是因为putsprintf是 C 标准库里的内容,但是ld并不会自动调用连接 C 标准库,需要手动调用。

为了修改这两个错误,简单修改的命令如下(这个命令生成的程序其实有问题):

ld main.o calc.o -o calc -I/lib64/ld-linux-x86-64.so.2 -lc -e main

这时候ld也不会再报错了,但是当你运行生成的可执行程序之后,你会发现虽然输出了Over Limit!,但是会报错Segmentation fault (core dumped)。如下:

$ ./calc
Over Limit!
Segmentation fault (core dumped)

出现Segmentation fault (core dumped)错误是因为连接的时候设置错误(库不对或者不够,设置出错),进而导致连接错误,最后生成的可执行程序会去访问不该访问的内存空间。

那么为了解决这个问题,完整的命令会很长。

如果是 Ubuntu 20.04,那么是:

ld -o calc -I /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccywuOTu.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o main /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. calc.o main.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o

如果是 Ubuntu(WSL),那么是:

$ ld -o calc -I /usr/lib/gcc/x86_64-linux-gnu/11/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/11/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper -plugin-opt=-fresolution=/tmp/cc4YfMoM.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o calc /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/11/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/11 -L/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/11/../../.. main.o calc.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/11/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crtn.o

这个命令有多长你可以复制下来看看。所以gcc生成的时候,就是调用ld的时候就是使用这样的命令,所以实际工作中需要连接的时候,还是直接使用gcc就可以了。

这样生成的可执行程序就工作很完美了,如下:

$ ./calc
Over Limit!

写这篇文章的过程中我学到了很多,也希望能帮到有需要的人~

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

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

相关文章

如何用Web服务组件IIS免费搭建站点,并实现外网远程访问?

作为一名程序猿&#xff0c;经常会有搭建网站的需求&#xff0c;或被朋友要求帮忙着搭建网站&#xff0c;但是如果将网站建设在个人电脑或公司的服务器上&#xff0c;面临的问题是&#xff0c;没有公网IP或屏蔽了外网的80端口&#xff0c;在外网环境下就无法直接内网的网站&…

chatgpt赋能python:将一行数变成列——Python简单实现

将一行数变成列——Python简单实现 在数据处理时&#xff0c;我们常常会遇到将一行的数据转换成列的情况&#xff0c;例如将多个数据在Excel表格中拆分为不同的列。这时候&#xff0c;Python可以帮助我们快速实现这个功能。 什么是Python&#xff1f; Python是一种高级&…

linux ubi文件系统加载失败“too large reserved_peds”

今天要升级linux根文件系统ubi&#xff0c;结果简单打包工作&#xff0c;就有一个始终不正确&#xff0c;花了不少时间&#xff0c;总算搞明白了。 我使用了两个打包脚本&#xff0c;脚本1是一个整个系统打包脚本&#xff0c;脚本2是一个专门给文件系统打包的脚本。 脚本1的部…

Vue框架和Axios实战:音乐播放器项目-歌手信息列表

Vue框架和Axios实战&#xff1a;音乐播放器项目-歌手信息列表 歌手信息一栏用于展示当前比较火热的歌手信息列表&#xff0c;首先我们创建静态热门歌手信息模型组件SingerList.vue&#xff0c;主要用于存放公共歌手列表信息&#xff0c;代码如下&#xff1a; 接着将歌手信息列表…

【前端 - HTML】第 5 课 - 表格标签

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、基本用法 3、表格结构标签 4、合并单元格 5、总结 1、缘起 在 HTML 中的表格标签 用于创建和展示数据表格。通过…

CAM, Grad-CAM, Grad-CAM++可视化CNN方式的代码实现和对比

当使用神经网络时&#xff0c;我们可以通过它的准确性来评估模型的性能&#xff0c;但是当涉及到计算机视觉问题时&#xff0c;不仅要有最好的准确性&#xff0c;还要有可解释性和对哪些特征/数据点有助于做出决策的理解。模型专注于正确的特征比模型的准确性更重要。 理解CNN…

【数据结构】排序篇

排序 一、排序的概念和应用1.1、排序的概念1.2、排序的应用1.3、常见的排序算法 二、插入排序2.1、直接插入排序2.2、希尔排序3.1.直接选择排序3.2、堆排序 四、交换排序4.1、冒泡排序4.2、快速排序4.2.1、hoare版本4.2.2、挖坑法4.2.3、前后指针版本4.2.4、快排非递归&#xf…

图结构的原理

引言 胡图图:“我成为电脑砖家(人们都在我吧上评论电脑配置).,按理说我应该开一家图图计算机研究科技公司…”! 于小美:“没错,图图应该开一家公司 来扩展你的专业知识” 何壮壮:“厉害是厉害 ,要不要大哥来帮帮你(至于钱,好说:月薪2万)…” 图图:“你狮子大开口! ,那你还是当…

『赠书活动--第三期』清华社赞助 | 《Python系列丛书》

『赠书活动 &#xff5c; 第三期』 本期书籍&#xff1a;《Python系列丛书》 Python从入门到精通(微课精编版) PyTorch深度学习简明实战 Django Web开发实例精解 Python分布式机器学习 Python Web深度学习 618&#xff0c;清华社 IT BOOK 多得图书活动开始啦&#xff01;活动…

Vue.js 中的 keep-alive 组件使用详解

Vue.js 中的 keep-alive 组件 在 Vue.js 中&#xff0c;keep-alive 组件是一个非常有用的组件&#xff0c;它可以帮助我们优化页面性能。在本文中&#xff0c;我们将介绍 keep-alive 组件是什么&#xff0c;如何使用它以及它的作用。 keep-alive 组件是什么&#xff1f; keep…

C plus plus ——【模板应用】

系列文章目录 C plus plus ——【模板应用】 文章目录 系列文章目录前言一、函数模板1.1、函数模板的定义1.2、函数模板的作用1.3、重载函数模板 二、类模板2.1、类模板的定义与声明2.2、简单类模板2.3、默认模板参数2.4、为具体类型的参数提供默认值 三、总结 前言 模板是C语…

Selenium Python教程第4章

4. 查找元素 在一个页面中有很多不同的策略可以定位一个元素。在你的项目中&#xff0c; 你可以选择最合适的方法去查找元素。Selenium提供了下列的方法给你: find_element_by_id find_element_by_name find_element_by_xpath find_element_by_link_text find_element_by_par…

自己制作智能语音机器人(基于jetson nano)

1 简介 如上图&#xff0c;主要采用jetson上编写python代码实现&#xff0c;支持离线语音唤醒、在线语音识别、大模型智能文档、在线语音合成。 所需硬件如下&#xff1a; jetson nano&#xff1a;linux科大讯飞麦克风硬件&#xff1a;AIUI R818麦克阵列开发套件6麦阵列&#…

华为全栈自主数据库GaussDB正式面向全球服务

一、前言 在6月7日举行的华为全球智慧金融峰会2023上&#xff0c;华为发布新一代分布式数据库GaussDB&#xff0c;并正式向全球客户提供服务。据介绍&#xff0c;GaussDB实现了核心代码100%自主研发&#xff0c;是国内当前唯一做到软硬协同、全栈自主的国产数据库。 可谓是里…

继承类的方法

1 问题 定义一个父类&#xff0c;用子类去继承父类所拥有的方法、定义属性&#xff0c;然后使用测试文件实现子类输出父类的方法信息&#xff0c;属性等。 2 方法 2.1 定义一个名为Person的父类&#xff1a; 2.2 定义一个名为Student的子类&#xff0c;并令其继承父类&#xff…

【PXIE301-211】基于PXIE总线架构的16路并行LVDS采集、1路光纤数据处理平台

PXIE301-211是一款基于PXIE总线架构的16路并行LVDS数据采集、1路光纤收发处理平台&#xff0c;该板卡采用Xilinx的高性能Kintex 7系列FPGA XC7K325T作为实时处理器&#xff0c;实现各个接口之间的互联。板载1组64位的DDR3 SDRAM用作数据缓存。板卡具有1个FMC&#xff08;HPC&am…

20道常考Python面试题大总结,让你轻松拿下大厂offer

关于Python的面试经验 一般来说&#xff0c;面试官会根据求职者在简历中填写的技术及相关细节来出面试题。 一位拿了大厂技术岗Special Offer的网友分享了他总结的面试经验。当时&#xff0c;面试官根据他在简历中所写的技术&#xff0c;面试题出的范围大致如下&#xff1a; …

国际化语言项目

基本概念 1、使用QString对象表示所有用户可见的文本。由于QString内部使用Unicode编码实现&#xff0c;所以它可以用 于表示所有需要向用户呈现的文本。当然&#xff0c;对于仅程序员可见的文本并不需要都变为QString对象&#xff0c;可利 用Qt提供的QCString或原始的“char …

VisualGLM训练缺失latest文件问题解决

清华已经公布了VisualGLM 模型&#xff0c;图像预测也取得了比较好的效果&#xff0c;但是我在调试微调的过程遇到不少问题&#xff0c;这里记录一下缺失latest问题解决&#xff08;ValueError: could not find the metadata file ../latest, please&#xff09; 修正后的代码可…

PyEMD算法解析

算法背景 经验模态分解&#xff08;Empirical Mode Decomposition&#xff0c;缩写EMD&#xff09;是由黄锷&#xff08;N. E. Huang&#xff09;在美国国家宇航局与其他人于1998年创造性地提出的一种新型自适应信号时频处理方法&#xff0c;特别适用于非线性非平稳信号的分析处…