当前位置: 首页 > news >正文

【Linux】基础开发工具(2)

1. 编译器gcc/g++

1-1 背景知识

1. 预处理(进⾏宏替换/去注释/条件编译/头⽂件展开等)
2. 编译(⽣成汇编)
3. 汇编(⽣成机器可识别代码)
4. 连接(⽣成可执行文件或库文件)

1-2 gcc编译选项

格式 gcc [ 选项 ] 要编译的⽂件 [ 选项 ] [ ⽬标⽂件 ]

1-2-1 预处理(进⾏宏替换)

预处理功能主要包括宏定义,⽂件包含,条件编译,去注释等。
预处理指令是以#号开头的代码⾏。
实例: gcc –E code.c –o code.i
选项“-E”,该选项的作⽤是让 gcc 在预处理结束后停⽌编译过程。
选项“-o”是指⽬标⽂件,“.i”⽂件为已经过预处理的C原始程序。

1-2-2 编译(⽣成汇编)

在这个阶段中,gcc ⾸先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的⼯作,
在检查⽆误后,gcc 把代码翻译成汇编语⾔。
⽤⼾可以使⽤“-S”选项来进⾏查看,该选项只进⾏编译⽽不进⾏汇编,⽣成汇编代码。
实例: gcc –S code.i –o code.s

1-2-3 汇编(生成机器可识别代码)

汇编阶段是把编译阶段⽣成的“.s”⽂件转成⽬标⽂件
读者在此可使⽤选项“-c”就可看到汇编代码已转化为“.o”的⼆进制⽬标代码了
实例: gcc –c code.s –o code.o

1-2-4 连接(⽣成可执⾏⽂件或库⽂件)

在成功编译之后,就进⼊了链接阶段。
实例: gcc code.o –o code

1-3 动态链接和静态链接

在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,但是每个源⽂件都是独⽴编译的,即每个*.c⽂件会形成⼀个*.o⽂件,为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执行的程序。这个链接的过程就是静态链接静态链接缺点很明显:
浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对
同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有
printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本;
更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程
序。但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在
执⾏的时候运⾏速度快。
动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在程序运行时才将它们链接在⼀起形成⼀个完整的程序,而不是像静态链接⼀样把所有程序模块都链接成⼀个单独的可执行⽂件。
动态链接其实远比静态链接要常用得多。比如我们查看下 hello 这个可执行程序依赖的动态库,会发现它就⽤到了⼀个c动态链接库:
# ldd 命令⽤于打印程序或者库⽂件所依赖的共享库列表。
在这⾥涉及到⼀个重要的概念:
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该
函数的声明,而没有定义函数的实现,那么,是在哪⾥实“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库⽂件中去了,在没有特别指定
时,gcc 会到系统默认的搜索路径“/usr/lib”下进⾏查找,也就是链接到 libc.so.6 库函数中去,这样
就能实现函数“printf”了,而这也就是链接的作⽤。

1-4 静态库和动态库

静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件比较⼤,但在运
行时也就不再需要库⽂件了。其后缀名⼀般为“.a”
动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执行文件中,⽽是在程序执行时由
运⾏时链接⽂件加载库,这样可以节省系统的开销。动态库⼀般后缀名为“.so”,如前⾯所述的
libc.so.6 就是动态库。gcc 在编译时默认使⽤动态库。完成了链接之后,gcc 就可以⽣成可执⾏⽂
件,如下所⽰。 gcc hello.o –o hello
gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证。

1-5 gcc其他常用选项

-E 只激活预处理,这个不⽣成⽂件,你需要把它重定向到⼀个输出⽂件⾥⾯
-S 编译到汇编语⾔不进⾏汇编和链接
-c 编译到⽬标代码
-o ⽂件输出到 ⽂件
-static 此选项对⽣成的⽂件采⽤静态链接
-g ⽣成调试信息。GNU 调试器可利⽤该信息。
-shared 此选项将尽量使⽤动态库,所以⽣成⽂件⽐较⼩,但是需要系统由动态库.
-O0
-O1
-O2
-O3 编译器的优化选项的4个级别,-O0表⽰没有优化,-O1为缺省值,-O3优化级别最⾼
-w 不⽣成任何警告信息。
-Wall ⽣成所有警告信息。

2. 自动化构建-make/Makefile

2.1 背景

会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒。
⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀
系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄
于进⾏更复杂的功能操作。
makefile带来的好处就是⸺“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全
⾃动编译,极⼤的提⾼了软件开发的效率。
make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这
个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可⻅,makefile
都成为了⼀种在⼯程⽅⾯的编译⽅法。
make是⼀条命令,makefile是⼀个⽂件,两个搭配使用,完成项目自动化构建。

2.2 基本使用

Makefile文件
  1 code: code.c2   gcc -o code code.c3 4 .PHONY: 5 clean:6   rm -rf code    
依赖关系
上⾯的⽂件code,它依赖code.c
依赖方法
gcc -o code code.c ,就是依赖方法
项目清理
⼯程是需要被清理的
像clean这种,没有被第⼀个⽬标⽂件直接或间接关联,那么它后⾯所定义的命令将不会被⾃动
执⾏,不过,我们可以显⽰要make执⾏。即命令⸺“make clean”,以此来清除所有的⽬标
⽂件,以便重编译。
但是⼀般我们这种clean的⽬标⽂件,我们将它设置为伪⽬标,⽤ .PHONY 修饰,伪目标的特性
是,总是被执行的。
可以将我们的 code   ⽬标⽂件声明成伪目标,测试⼀下。
声明前
make不总是被执行的

声明后: 

 

make总是被执行 

 

 什么叫做总被执行?

Make 工具通过对比文件的 “修改时间(Modify Time)” 来判断是否需要重新构建目标。

文件 = 内容 + 属性

Modify: 内容变更,时间更新。
Change :属性变更,时间更新。
Access :常指的是⽂件最近⼀次被访问的时间。在 Linux 的早期版本中,每当⽂件被访问时,其 atime都会更新。但这种机制会导致⼤量的IO 操作。

.PHONY 标记 code 为伪目标不会因 Make 的默认判断逻辑(对比文件修改时间、检查目标文件是否存在 )而跳过。

make是如何⼯作的,在默认的⽅式下,也就是我们只输⼊make命令。那么:
1. make会在当前⽬录下找名字叫“Makefile”或“makefile”的⽂件。
2. 如果找到,它会找⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会找到 myproc 这
个⽂件,并把这个⽂件作为最终的⽬标⽂件。
3. 如果 myproc ⽂件不存在,或是 myproc 所依赖的后⾯的 myproc.o ⽂件的⽂件修改时间要
⽐ myproc 这个⽂件新(可以⽤ touch 测试),那么,他就会执⾏后⾯所定义的命令来⽣成
myproc 这个⽂件。
4. 如果 myproc 所依赖的 myproc.o ⽂件不存在,那么 make 会在当前⽂件中找⽬标为
myproc.o ⽂件的依赖性,如果找到则再根据那⼀个规则⽣成 myproc.o ⽂件。(这有点像⼀
个堆栈的过程)
5. 当C源文件(.c)和头文件(.h)存在时,make会先依据规则将C文件编译为目标文件(.o),例如通过`gcc -c code.c -o code.o`生成`code.o`;接着利用该目标文件执行链接操作,生成最终的可执行程序,如`gcc code.o -o code`,此过程会自动处理依赖关系,完成从源代码到可执行文件的构建。
6. 这就是整个make的依赖性,make会⼀层⼜⼀层地去找⽂件的依赖关系,直到最终编译出第⼀个
⽬标⽂件。
7. 在找寻的过程中,如果出现错误,比如最后被依赖的⽂件找不到,那么make就会直接退出,并
报错,⽽对于所定义的命令的错误,或是编译不成功,make根本不理。
8. make只管⽂件的依赖性,即,如果在我找了依赖关系之后,冒号后⾯的⽂件还是不在,那么对
不起,我就不工作啦。

2.3 适度扩展语法

BIN=proc.exe                     # 定义变量
CC=gcc
#SRC=$(shell ls *.c)             # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c)              # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名
OBJ=$(SRC:.c=.o)                 # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表
LFLAGS=-o                        # 链接选项
FLAGS=-c                         # 编译选项
RM=rm -f                         # 引⼊命令$(BIN):$(OBJ)                          # $ 符号用于引用变量@$(CC) $(LFLAGS) $@ $^             # $@:表示当前目标文件。 $^: 代表所有依赖⽂件列表@echo "linking ... $^ to $@"
%.o:%.c                                # %.c 展开当前⽬录下所有的.c。 %.o: 同时展开同名.o@$(CC) $(FLAGS) $<                 # $<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。@echo "compling ... $< to $@"      # @:不回显命令
.PHONY:clean
clean:$(RM) $(OBJ) $(BIN)                # $(RM): 替换,⽤变量内容替换它.PHONY:test
test:@echo $(SRC)@echo $(OBJ)

 

相关文章:

  • 重定向攻击与防御
  • Docker 入门教程(二):Docker 的基本原理
  • 东芝e-STUDIO 2323AMW双面复印报计数器溢出故障
  • 《规则怪谈》合集
  • Kotlin环境搭建与基础语法入门
  • python pandas数据清洗
  • Python打卡:Day37
  • 黑马JVM解析笔记(四):Javap图解指令流程,深入理解Java字节码执行机制
  • 【办公类-105-01】20250626 托小班报名表-条件格式-判断双胞胎EXCLE
  • MySQL (一):数据类型,完整性约束和表间关系
  • C++智能指针概念及std::unique_ptr使用介绍
  • YOLOv10tensorRT推理代码C++
  • Apipost和Postman对比
  • Git-git worktree的使用
  • [OS_27] 现代应用程序架构 | Full System Emulation | VM | Docker
  • springcloud 尚硅谷 看到9开头
  • C++(模板与容器)
  • 【学习笔记】Dify知识库配置参数解析
  • 跨越十年的C++演进:C++14新特性全解析
  • CNN不是一个模型?