Linux开发工具有以下几种:
- vs-集成开发环境
- 写代码-vim
- 编译代码-gcc/g++
- 调试代码-gdb
- 维护项目结构、自动换编译-make+makefile
yum
yum工具在每次安装指定软件包的时候,都会检测源服务器上的软件包信息,为了便捷不用每次都去搜索软件包信息,因此使用 yum makecache将软件包信息缓存到本地,使用 yum clean all 清理老旧的缓存信息。
yum search 用于在搜索包含有指定关键字的软件包
yum -y update:升级所有包同时,也升级软件和系统内核;
yum -y upgrade:只升级所有包,不升级软件和系统内核,软件和内核保持原样。
vim的批量化注释
gcc相关
-
gcc常见选项
-c 汇编完成后停止,不进行链接
-E 预处理完成后停止,不进行编译
-S 编译完成后停止,不进行汇编
-o 用于指定目标文件名称
-g 生成debug程序。向程序中添加调试符号信息
预处理
- 条件编译的功能
条件编译的主要作用是根据不同的条件选择性地包含或排除某些代码片段,从而在不同的情况下生成不同的目标代码。即"我们可以通过给编译器传递不同的宏值,来进行对代码的动态裁减!"
同时 #ifndef __CODE_H_ 可以防止头文件被重复包含。
- 为什么要进行条件编译
- 平台兼容性:在不同的操作系统或硬件平台上,可能需要使用不同的代码实现来适应各种环境。条件编译可以根据不同的平台选择性地包含或排除特定代码,以确保程序在不同平台上都能正确运行。
- 功能选择:在开发过程中,可能会根据用户需求或特定配置选择包含或排除某些功能模块。通过条件编译,可以根据需求选择性地编译不同的功能实现,以满足不同的需求。
- 调试和测试:在调试和测试阶段,可能需要临时增加或排除某些代码片段来辅助调试或测试。条件编译可以用于临时性地控制代码的包含与排除。
补充:如果你是用C语言写一个源程序,经过预处理阶段后生成文件中的语言仍然是C语言。
编译
-
编译器的介绍
编译器是一种将源代码转换为目标代码的程序。它接受用高级编程语言编写的源代码作为输入,并将其翻译成特定目标平台上的机器代码或中间代码。编译器通常由多个阶段组成,每个阶段负责执行特定的任务,最终生成可执行程序或库文件。
以下是编译器通常包括的几个主要阶段:
-
词法分析(Lexical Analysis):也称为扫描器,从左向右,从上往下扫描,负责将源代码转换为词法单元(tokens),如关键字、标识符、常量等。
-
语法分析(Syntax Analysis):也称为解析器,将词法单元转换为语法树(parse tree)或抽象语法树(abstract syntax tree,AST),以表示源代码的结构和语法。
-
语义分析(Semantic Analysis):进行类型检查、符号表管理等工作,确保程序符合语言的语义规则。
-
中间代码生成(Intermediate Code Generation):将语法树转换为中间表示形式,如三地址码、四地址码等。
-
优化(Optimization):对中间代码进行优化,以提高程序的性能和效率。
-
代码生成(Code Generation):将优化后的中间代码转换为目标平台上的机器代码或汇编代码。
除了以上的基本阶段外,编译器还可能包括其他特定于语言或平台的额外阶段,如目标代码优化、目标平台适配等。
-
编译阶段的编译优化
- 死代码删除是编译最优化技术,指的是移除根本执行不到的代码,或者对程序运行结果没有影响的代码,而并不是删除被注释的代码;
- 内联函数,也叫编译时期展开函数, 指的是建议编译器将内联函数体插入并取代每一处调用函数的地方,从而节省函数调用带来的成本,使用方式类似于宏,但是与宏不同的是内联函数拥有参数类型的校验,以及调试信息,而宏只是文本替换而已;
- for循环的循环控制变量,通常被cpu访问频繁,因此如果调度到寄存器中进行访问则不用每次从内存中取出数据,可以提高访问效率;
- 强度削弱是指执行时间较短的指令等价的替代执行时间较长的指令,比如 num % 128 与 num & 127 相较,则明显&127更加轻量。
汇编
汇编过程是将汇编语言源代码转化为可执行的机器码的过程。它包括了一系列的步骤,从源代码到最终的可执行文件。
下面是汇编过程的主要步骤:
-
编写源代码:首先,程序员使用适当的文本编辑器编写汇编语言源代码。在源代码中,程序员使用助记符和符号来表示指令、寄存器、内存地址等。
-
汇编器(Assembler):源代码被传递给汇编器,汇编器会对源代码进行词法分析和语法分析,将汇编语言的助记符转化为对应的机器指令,并根据操作数的类型生成二进制表示。汇编器还会处理伪指令(如定义变量、设置程序入口点等)和符号解析(如标签的地址计算)等操作。
-
目标文件生成:汇编器将转换后的机器指令和数据存储在目标文件中。目标文件包含了可执行代码的二进制表示,以及其他与程序执行相关的信息,如符号表、重定位信息等。
-
链接器(Linker):如果程序由多个源代码文件组成,那么每个源文件经过汇编生成的目标文件需要被链接在一起形成最终的可执行文件。链接器的任务是解析并处理目标文件之间的符号引用和重定位信息,将它们合并成一个单一的可执行文件。链接器还会添加必要的系统库和运行时支持代码,以确保程序能够正确执行。
-
可执行文件生成:经过链接器处理后,最终的可执行文件就生成了。可执行文件包含了操作系统可以直接执行的二进制代码,当用户运行该文件时,操作系统会加载并执行其中的指令,实现相应的功能。
需要注意的是,汇编过程往往是与特定的计算机体系结构和操作系统相关的。不同的计算机体系结构和操作系统可能有不同的汇编语言和汇编工具链。汇编过程中还可以进行优化等额外的步骤,以提高程序的性能和效率。
链接
链接是将多个目标文件合并成为一个可执行文件的过程。在程序开发中,为了方便管理和维护,通常将一个大程序分解成多个源文件,每个源文件编译后生成一个目标文件,最终通过链接将这些目标文件合并成为一个可执行文件。
下面是链接过程的主要步骤:
-
符号解析:首先,链接器需要对目标文件进行符号解析。符号是指程序中的变量、函数名等标识符。在链接过程中,不同的目标文件可能会引用相同的符号,因此需要解析这些符号,并将它们映射到唯一的内存地址上。
-
重定位:链接器还需要对目标文件进行重定位。因为不同的目标文件编译时可能使用不同的内存地址,当多个目标文件链接在一起时,需要将它们的内存地址重新定位,以确保程序能够正确地访问各种数据和代码。
-
合并目标文件:链接器将经过符号解析和重定位处理的目标文件合并在一起,生成一个单一的输出文件,通常是可执行文件或库文件。
-
解析库文件:如果程序使用了库文件,链接器还需要将库文件中的目标文件解析并添加到输出文件中。库文件是一些预编译的代码和数据的集合,程序可以通过链接库文件来使用其中的函数和数据。
需要注意的是,不同的操作系统和编译器可能有不同的链接方式和规则。例如,在Linux系统中,链接器通常是GNU链接器(ld),而在Windows系统中,链接器通常是Microsoft Visual Studio中的链接器(link.exe)。
-
动静态库的优缺点
动态库和静态库是在软件开发中常见的两种库文件形式,它们各自具有不同的优点和缺点。
动态库(Dynamic Library)的优点:
- 共享性:动态库可以在多个程序之间共享,减少存储空间的占用。多个程序可以引用同一个动态库,而不需要每个程序都包含该库的完整副本。
- 更新方便:如果动态库需要更新,只需替换动态库文件即可,无需重新编译和链接整个程序。这样可以更快速地更新和发布软件的新版本。
- 运行效率:动态库在程序运行时被加载到内存中,并按需进行链接,这意味着程序的启动时间较短。此外,如果多个程序同时使用同一个动态库,那么在内存中只需要保留一份库的实例,减少了内存的占用。
动态库的缺点:
- 额外依赖:使用动态库的程序需要确保目标系统已经安装了相应的动态库。如果目标系统没有安装所需的动态库,程序将无法正常运行。
- 可移植性:由于不同操作系统和平台对动态库的支持不同,所以在跨平台开发时可能需要处理一些兼容性问题。
静态库(Static Library)的优点:
- 独立性:静态库是编译链接到程序中的,因此程序本身包含了库的完整副本,可以独立运行,无需依赖外部库文件。
- 部署方便:将程序和静态库打包在一起,可以方便地部署和分发,不需要担心目标系统是否已经安装了相应的动态库。
静态库的缺点:
- 冗余性:由于每个使用静态库的程序都会包含一份库的完整副本,可能导致存储空间的浪费。如果多个程序使用相同的静态库,那么每个程序都会有一份冗余的库代码。
- 更新麻烦:如果静态库需要更新,需要重新编译和链接所有使用该库的程序,增加了维护和更新的工作量。
选择使用动态库还是静态库,需要根据具体的需求和情况来考虑。如果希望减少存储空间占用、方便更新和共享,动态库是一个不错的选择。而如果需要独立性和部署方便,静态库可能更适合。
补充:ldd 命令可以用来查看一个可执行程序所依赖的库。
动态库文件通常以 .so
(共享对象)的扩展名结尾,而静态库文件通常以 .a
(静态库归档)的扩展名结尾
-
静态链接和动态链接
动态链接(Dynamic Linking)和静态链接(Static Linking)是在链接阶段将库文件与可执行文件结合的两种不同方法。
静态链接:
- 静态链接将库文件的代码和数据完整地复制到可执行文件中。在编译和链接过程中,编译器将源代码编译为目标文件,链接器将目标文件和静态库文件合并成一个单独的可执行文件。
- 静态链接生成的可执行文件独立于外部库文件,可以在没有外部库文件的情况下运行。所有需要的函数和数据都包含在可执行文件中,因此程序的移植性较好。
- 静态链接的主要优点是部署方便,并且不会受到外部库版本变化的影响。但是,每个可执行文件都包含了库的完整副本,可能导致存储空间浪费。
gcc -o mybin-static mytest.c -static
动态链接:
- 动态链接将库文件的代码和数据保留在独立的库文件中,而可执行文件中只包含对库文件的引用。在程序运行时,操作系统会将所需的库文件加载到内存中,并将其链接到可执行文件中。
- 动态链接生成的可执行文件较小,因为它只包含了对外部库的引用,而不是实际的库代码和数据。这减少了可执行文件的大小和启动时间。
- 动态链接的主要优点是共享性和更新方便。多个程序可以共享同一个库文件,减少存储空间占用。如果库文件需要更新,只需替换库文件即可,无需重新编译和链接整个程序。
- 动态链接的缺点是需要确保目标系统已经安装了所需的库文件,否则程序将无法正常运行。此外,动态链接可能导致一些兼容性问题。
gcc -o mybin mytest.c
注意:动态链接是gcc默认的行为!!
同时我们也可以看到两个链接文件的体积大小:
从这里也可以看出两种链接方法的部分优缺点:静态链接所占用更多的磁盘空间、加载运行时占用更多的内存空间、下载时占用更多的网络空间。
make/Makefile
- Makefile和make形成目标文件的时候,默认是从上到下扫描makefile文件的,默认形成的是第一个目标文件
- 默认只形成一个最新的可执行文件,如果这个这个可执行文件没有被更改,再使用make时,也不会进行编译!
make和Makefile怎么知道可执行程序是比较新的呢??
这个是通过对比时间比出来的,只要可执行程序的最近修改时间比所有源文件的最近修改时间新,说明它就是最新的!
认识一下时间
可以通过 stat 指令查看一个文件的相关时间:
-
Access time(访问时间):指的是文件最后一次被访问(打开、查看)的时间。当文件被读取时,其访问时间会更新。
-
Modify time(修改时间):表示文件内容最后一次被修改的时间。当文件被编辑并保存时,其修改时间会更新。
-
Change time(更改时间):指的是文件元数据(metadata)(属性)最后一次被修改的时间,比如文件权限、所有者等。当文件的元数据发生变化时,其更改时间会更新。
这里当一个文件的内容发生改变时,Modify 会跟新;当一个文件的属性发生改变时,Change 会发生改变。当一个文件的内容发生改变时,这个文件的大小等属性也会发生改变,所以 Modify 改变会联动改变 Change。
补充:Access time在访问文件时会更新,但是有些操作系统(例如Linux)会采用一定的优化策略,减少对于硬盘的频繁读取操作。具体来说,当一个文件被连续地访问多次时,操作系统为了提高性能,可能会将该文件缓存在内存中,而不是每次都去硬盘上读取该文件。
此时,虽然文件被访问了多次,但是因为文件已经被缓存到内存中,所以Access time不会变化。这样做可以避免频繁读取硬盘的开销,提高系统的响应速度和效率。
make和Makefile通过Modify时间来判断这个可执行程序是不是最新生成的。
可以通过,touch 指令更新源文件的时间。
语法补充
1).PHONY
所以,当一个文件的时间没有更新,也可以通过这种方法进行make指令的编译操作。
通常这个语法会用来修饰clean,保证我们每次执行make clean时能够正确清理可执行文件。
在Makefile中将源文件设置为伪文件(.PHONY)一般是不建议的。这是因为伪文件是指那些不代表实际文件的目标,而是代表一些特定的操作或命令。主要有以下几个原因:
-
避免不必要的重编译:如果将源文件设置为伪文件,每次执行make时,会强制重新执行与该伪文件相关的命令,包括依赖的命令。这样会导致不必要的重编译,降低了构建系统的效率。
-
破坏依赖关系:Makefile的核心功能是根据依赖关系判断哪些文件需要更新。将源文件设置为伪文件会破坏这种依赖关系,使得构建系统无法正确地判断是否需要重新编译相关的目标文件(当源文件较多时,仅仅修改其中一个源文件就要将所有的源文件重新编译,这样会延长编译时间)。
2)变量
这个指令通常出现在 Makefile 规则的命令部分,用于编译生成目标文件。
具体来说,这个指令的含义如下:
gcc
:是调用 GCC (GNU Compiler Collection)进行编译的命令。-o $@
:表示生成的目标文件的文件名。$@
是一个自动化变量,在 Makefile 中表示规则中的目标文件。$^
:表示所有的依赖文件列表。$^
是一个自动化变量,在 Makefile 中表示规则中的所有依赖文件。
因此,指令 gcc -o $@ $^
的作用是使用 GCC 编译生成目标文件,其中目标文件的文件名由 $@
表示,所有的依赖文件列表由 $^
表示。这样的指令通常出现在 Makefile 的规则中,用于根据依赖关系将源文件编译成目标文件。
这个语句是一个简单的 Makefile 规则,用于描述如何编译生成可执行文件 "mybin"。下面是对这个语句的解释:
cc=gcc
:这行代码定义了一个变量 cc,并赋值为 "gcc",表示编译器为 GCC。flag=-o
:这行代码定义了一个变量 flag,并赋值为 "-o",表示编译选项中的输出文件指定。mybin: test.c
:这行代码表示规则头,表明 mybin 是依赖于 test.c 文件的目标。也就是说,如果 test.c 文件发生了改变,或者 mybin 不存在,就需要执行下面的命令来生成 mybin。
接下来是规则的命令部分:
$(cc) $(flag) $@ $^
:这行代码表示具体的命令。$(cc)
和$(flag)
分别引用了前面定义的变量 cc 和 flag。$@
是一个自动化变量,在 Makefile 中表示规则中的目标文件(这里就是 mybin)。$^
也是一个自动化变量,在 Makefile 中表示规则中的所有依赖文件(这里就是 test.c)。因此,这行命令的作用是使用 gcc 编译 test.c 文件,并将生成的可执行文件命名为 mybin。
也可以这样使用:
3)关于make/Makefile语法推导理解
进度条设计
1、预备的小知识
回车和换行
\n -- 换行+回车
\t -- 回车,让光标回到此行的开头
倒计时
#include <stdio.h>
#include <unistd.h>
int main()
{
int cnt = 9;
while(cnt >= 0)
{
printf("%-2d\r", cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
return 0;
}
2、编写进度条
main.c
#include "processbar.h"
#include <stdlib.h>
#include <time.h>
#define FILESIZE 1024*1024*1024
// 设计一个下载场景
void download(callback_t cb)
{
srand(time(NULL)^1023);
int total = FILESIZE;
while(total)
{
usleep(1000);
int one = rand()%(1024*1024);// 一次下载的量
total -= one;
if(total < 0) total = 0;
// 利用当前的进度刷新进度条
int download = FILESIZE - total; // 已经下载的体积
double rate = (download*1.0/(FILESIZE))*100.0; // 下载的进度设计为百分比的格式
cb(rate);
}
}
int main()
{
download(process_flush); // 使用回调函数的方式刷新进度条
return 0;
}
processbar.h
#pragma once
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define NUM 103
#define BODY '='
#define HEAD '>'
typedef void(*callback_t)(double);
// 第一个版本,测试进度条
void process();
// 第二个版本,根据外部下载的速度刷新进度条
void process_flush(double);
processbar.c
#include "processbar.h"
const char* label = "|/-\\";
// 第一个版本
void process()
{
char buffer[NUM];
memset(buffer, '\0', sizeof(buffer));
int cnt = 0;
int n = strlen(label);
buffer[0] = HEAD;
while(cnt <= 100)
{
printf("[%-100s][%3d%%][%c]\r", buffer, cnt, label[cnt%n]);
fflush(stdout);
buffer[cnt++] = BODY;
if(cnt < 100) buffer[cnt] = HEAD;
usleep(50000);
}
printf("\n");
}
// 第二个版本
char buffer[NUM] = { 0 };
void process_flush(double rate)
{
int n = strlen(label);
static int cnt = 0; // 用来刷新运行图
// 如果进度小于百分之一,只打印一个箭头
if(rate <= 1.0) buffer[0] = HEAD;
printf("[%-100s][%.1f%%][%c]\r", buffer, rate, label[cnt%n]);
fflush(stdout);
buffer[(int)rate] = BODY;
if((int)rate < 99) buffer[(int)rate + 1] = HEAD;
if(rate >= 100) printf("\n"); // 打印最后一次后换行
cnt++;
cnt %= n;
}
Git
1、git版本控制器
Git 是一款分布式版本控制系统,它的主要作用是记录文件的修改历史和版本信息,并在需要时进行版本回退、分支管理等操作。与传统的集中式版本控制系统相比,Git 的最大优势在于它是分布式的,每个开发者都可以拥有自己的本地仓库,而不必依赖于中央服务器。这样就可以更加方便地进行团队协作,同时也更加安全可靠。
2、Git VS Gitee && GitHub 区别
Git 是版本控制器,而 Gitee 和 GitHub 则是代码托管平台。它们都提供了基于 Git 的版本管理功能,可以让开发者将代码存储到远程仓库中,并进行版本控制、分支管理等操作。
虽然 Git、Gitee 和 GitHub 都是基于 Git 的,但它们之间还是存在一些区别:
- Git 是一个版本控制器,它是一个命令行工具,需要在本地使用,而 Gitee 和 GitHub 则是基于互联网的代码托管平台,可以通过网页或客户端进行访问和使用。
- Git 可以在本地创建仓库、添加文件、提交代码等操作,而 Gitee 和 GitHub 则需要先在网站上创建远程仓库,然后再将本地仓库与远程仓库进行关联。
- Gitee 和 GitHub 提供了代码托管平台的功能,可以让开发者在平台上进行协作、交流等操作,而 Git 并没有这些功能。
- Gitee 和 GitHub 也提供了一些其他的功能,比如 Issue、Wiki、Pull Request 等,可以帮助开发者更加方便地进行项目管理和协作。
gdb
今天的分享就到这里了,如果,你感觉这篇博客对你有帮助的话,就点个赞吧!感谢感谢……