GCC
如何对制作完成的.c文件进行编译
GCC: 编译套件
-
GCC原名为GNU C语言编译器(GNU C Compiler)
-
GCC(GNU Compiler collection, GNU编译器套件)是由 GNU开发的编程语言译器。GNU编译器套件包括C、C++、Objective-c、Java、Ada和 Go语言前端,也包括了这些语言的库(如libstdc++, libgcj等)
-
GCC不仅支持C的许多"方言",也可以区别不同的c语言标准;可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个C标准。如:当使用命令行参数 '-std=c99` 启动GCC时,编译器支持C99标准。
-
安装命令sudo apt install gcc g++(版本> 4.8.5 :支持C++11特性)
-
查看版本 gcc/g++ -v/–version
-
编译程序: gcc test.c -o app(aoo为指定输出文件名 默认a.out)
编程语言的发展:
GCC工作流程
- 源代码 .h .c .cp
- (预处理)预处理器: 对头文件进行展开;将代码注释删除;进行宏替换 输出文件: .i
- (编译器)汇编代码:输出文件: .s
- (汇编器)目标代码:输出文件: .o (计算机能够识别的目标指令)
- (链接器)目标代码 + 启动代码 + 库代码 + 其他目标代码 -> 可执行程序 输出文件: .exe .out
GCC常用参数选项:
# (-E: 对test.c 进行预处理, -o: 生成目标文件 app)
gcc test.c -E -o app
# (直接对源文件进行 -S 那么会一次性执行 预处理 和 编译操作)
gcc test.c -S
# 在程序编译的时候,指定一个宏(DEBUG)-DDEBUG
# 方便调试信息的输出 Log输出
gcc test.c -o test -D DEBUG
# -Wall 生成所有警告信息
gcc test.c -o test -Wall
# -On n的取值范围:0~3。编译器的优化选项的4个级别,-○0表示没有优化,-o1为缺省值,-o3优化级别最高
# 效果 可以达到反汇编的效果,通过优化将一些代码逻辑信息隐藏 ; 还有性能优化等
# 如int a = 10; int b = a; 优化后 int a = 10; int b = 10;
gcc test.c -o test -O1
#include <stdio.h>
int main()
{
#ifdef DEBUG
printf("aaaaaaa\n");
#endif
printf("Hello World\n");
return 0;
}
注意:发布Release版本 需要把所有Log信息输出都去除,所以
- 在调试期间,可以加上宏进行Log信息输出 。./main DEBUG
- 在Release时,就不加上宏防止Log信息输出 ./main
gcc vs. g++
gcc 和 g++ 都是GNU(组织)的一个编译器。
-
误区一:gcc只能编译c代码,g++只能编译c++代码。其实两者都可以。
- 请注意:后缀为 .c 的, gcc把它当作是C程序,而g++当作是 c++程序
- 后缀为 .cpp 的,两者都会认为是C++程序,C++的语法规则更加严谨一些
- 编译阶段, g++会调用gcc,对于C++代码,两者是等价的.但是因为gcc命令不能自动和C++程序使用的库链接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接都用g++ 了。
-
误区二:gcc 不会定义__cplusplus 宏,而g++会
- 实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释
- 如上所述,如果后缀为.c,并且采用gcc 编译器,则该宏就是未定义的,否则就是已定义
-
误区三:编译只能用gcc,链接只能用g++
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。
- gcc命令不能自动和C++程序使用的库链接,所以通常使用g++来完成链接。但在编译阶段,g++会自动调用gcc,二者等价
库
- 库文件是计算机上的一类文件,可以简单的把库文件看成―种代码(二进制代码)仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。
- 库是特殊的一种程序.编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
- 库文件有两种:静态库和动态库(共享库)
- 静态库:在程序的链接阶段被复制到了程序中;
- 动态库:在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
- 库的好处: 1. 代码保密 2. 方便部署和分发
工作原理:
-
静态库:GCC进行链接时,会把静态库的代码打包到可执行程序中
-
动态库:GCC进行链接时,动态库的代码不 会被打包到可执行程序中
-
程序启动之后,(当运行到使用动态库里面 API 的时候) (整个)动态库会被动态加载到内存中,通过ldd (list dynamic dependencies)命令检查动态库依赖关系
boyangcao@MyLinux:~/Linux/Lesson06/library$ ldd main linux-vdso.so.1 (0x00007ffc995f7000) libcalc.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f99cb99c000) /lib64/ld-linux-x86-64.so.2 (0x00007f99cbf8f000)
libcalc.so => not found 没有找到目标动态库地址
-
如何定位共享库文件呢?
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 -> 环境变量LD_LIBRARY_PATH -> /etc/ld.so.cache文件列表 -> /lib/,/usr/lib目录找到库文件后将其载入内存。
静态库的制作
命名规则:
-
Linux : libxxx.a
lib:前缀(固定)
xxx:库的名字,自己起
.a:后缀(固定)
-
windows : libxxx.lib
静态库的制作:
-
gcc获得.o 文件
gcc -c add.c div.c mult.c sub.c
-
将.o 文件打包,使用ar 工具(archive)
ar rcs libxxx.a xxx.o xxx.o
- r :将文件(.o)插入备存文件(libxxx.a)中
- c :建立备存文件(libxxx.a)
- s :索引
# -I directory 指定include包含文件的搜索目录
gcc main.c -o app -I ./include/
# -l 在程序编译的时候,指定使用的库
# -L 指定编译的时候,搜索的库的路径。
gcc main.c -o app -I ./include/ -l calc -L ./lib/
动态库的制作
(共享库)
命名规则:
- Linux:libxxx.so
- lib:前缀(固定)
- xxx: 库的名字,自己起
- .so:后缀(固定)
在Linux下是一个可执行文件
- windows : libxxx.dll
动态库的制作:
-
gcc得到.o文件,得到和位置无关的代码
gcc -c -fpic a.c b.c # gcc -c -fPIC a.c b.c # -fpic/-fPIC 生成与位置无关的代码:用于编译阶段,产生的代码没有绝对地址,全部用相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic ,那么生成的代码就会与位置有关,当进程使用该.so文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。
-
gcc得到动态库
gcc -shared a.o b.o -o libcalc.so
动态库的使用:
-
将制作好的动态库放入目标工程目录下(./lib/)
-
编译main.c得到可执行文件main
(附加:参数 -I 指定include查询目录 参数-L 指定搜索的库的路径 参数-I 指定使用的库)
gcc main.c -o main -I ./include/ -L ./lib/ -l calc
-
配置动态库地址
- 第一种方法:环境变量LD_LIBRARY_PATH
# 当前主机/虚拟机下的环境配置信息(以键值对形式出现) (PATH:环境变量 在其中寻找调用的函数) env # 配置环境变量LD_LIBRARY_PATH 使系统的动态载入器获取该动态库的绝对路径 # export :添加环境变量 $LD_LIBRARY_PATH: : 获取原先LD_LIBRARY_PATH的内容 (:后跟新添加的内容) export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/boyangcao/Linux/Lesson06/library/lib
# 现在输出LD_LIBRARY_PATH环境变量的内容就可以看到目标动态库路径了 boyangcao@MyLinux:~/Linux/Lesson06/library$ echo $LD_LIBRARY_PATH :/home/boyangcao/Linux/Lesson06/library/lib boyangcao@MyLinux:~/Linux/Lesson06/library$ ldd main linux-vdso.so.1 (0x00007ffd893a1000) libcalc.so => /home/boyangcao/Linux/Lesson06/library/lib/libcalc.so (0x00007fbd04e4d000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbd04a5c000) /lib64/ld-linux-x86-64.so.2 (0x00007fbd05251000)
不过这个临时配置环境变量,关闭终端后重新打开就会消失。
永久配置环境变量方法:
-
用户级别
vim .bashrc # 1. 将export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/boyangcao/Linux/Lesson06/library/lib # 插入.bashrc即可 # 2. 运行(更新).bashrc . .bashrc source .bashrc
-
系统级别
sudo vim /etc/profile # 1. 将export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/boyangcao/Linux/Lesson06/library/lib # 插入.bashrc即可 # 2. 运行(更新)/etc/profile source /etc/profile
- 第二种方法:/etc/ld.so.cache文件列表
# 都是二进制数据 无法直接修改 vim /etc/ld.so.cache # 通过这里间接进行配置 sudo vim /etc/ld.so.conf # 将 /home/boyangcao/Linux/Lesson06/library/lib 保存里面即可 # 更新 sudo ldconfig
- 第三种方法:/lib/,/usr/lib目录 (不建议这么做)
本身就包含了系统的很多库文件,如果写入自己的库文件可能会把系统库文件替换,可能会出现问题。
静态库 vs. 动态库
程序编译成可执行程序的过程:
静态库、动态库区别来自链接阶段如何处理,链接成可执行程序。分别称为静态链接方式和动态链接方式。
- 静态库的优缺点:
- 优点:
- 静态库被打包到应用程序中加载速度快
- 发布程序无需提供静态库,移植方便
- 缺点:
- 消耗系统资源,浪费内存
- 更新、部署、发布麻烦(每次更新需要重新编译)
- 优点:
- 动态库的优缺点:
- 优点:
- 可以实现进程间资源共享(共享库) (只需要加载进入内存一次)
- 更新、部署、发布简单
- 可以控制何时加载动态库
- 缺点
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库
- 优点:
MakeFile
- 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译.哪些文件需要后编译.哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile文件就像一个shell脚本一样,也可以执行操作系统的命令。
- Makefile 好处:"自动化编译”,一旦写好,只需要一个 make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释Makefile 文件中指令的命令工具,大多数的 IDE都有这个命令,比如Delphi 的 make, Visual C++的nmake,Linux 下GNU 的make。
Makefile文件命名和规则:
-
文件命名:makefile或者Makefile
-
Makefile 规则
-
一个 Makefile 文件中可以有一个或者多个规则
-
目标…:依赖…
命令(shell命令) …
app:sub.c add.c mult.c div.c main.c gcc sub.c add.c mult.c div.c main.c -o app
-
目标:最终要生成的文件(伪目标除外)
-
依赖:生成目标所需要的文件或是目标
-
命令:通过执行命令对依赖操作生成目标(命令前必须Tab缩进)
-
-
-
-
Makefile 中的其它规则一般都是为第一条规则服务的。
(注意:这种方式效率更高(分开写),这样的话当某个.c文件修改后,只需要对该.c文件进行重新编译,其他.c文件不会重新编译,然后最后链接一下 更新app即可)
app:sub.o add.o mult.o div.o main.o gcc sub.o add.o mult.o div.o main.o -o app sub.o:sub.c gcc -c sub.c -o sub.o add.o:add.c gcc -c add.c -o add.o mult.o:mult.c gcc -c mult.c -o mult.o div.o:div.c gcc -c div.c -o div.o main.o:main.c gcc -c main.c -o main.o
工作原理:
-
命令在执行之前,需要先检查规则中的依赖是否存在
- 如果存在,执行命令;
- 如果不存在.向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
-
检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
-
如果依赖的时间比目标的时间晚,需要重新生成目标
-
如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
boyangcao@MyLinux:~/Linux/Lesson07$ make make: “app”已是最新。 boyangcao@MyLinux:~/Linux/Lesson07$ vim main.c boyangcao@MyLinux:~/Linux/Lesson07$ make gcc -c main.c -o main.o gcc sub.o add.o mult.o div.o main.o -o app boyangcao@MyLinux:~/Linux/Lesson07$ vim Makefile
-
变量:
- 自定义变量:变量名=变量值 var=hello $(var)
- 预定义变量:
- AR:归档维护程序的名称,默认值为ar
- CC : c编译器的名称,默认值为cc
- CXX : C++编译器的名称,默认值为g++
- $@:目标的完整名称
- $<:第一个依赖文件的名称
- $^:所有的依赖文件
- 获取变量的值: $(变量名)
app:main.c a.c b.c
gcc -c main.c a.c b.c
#自动变量只能在规则的命令中使用
app:main.c a.c b.c
$(cc) -c $^ -o $@
# define varience
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
$(CC) $(src) -o $(target)
模式匹配:
- %.o:%.c
- %:通配符,匹配一个字符串
- 两个%匹配的是同一个字符串
%.o:%.c
gcc -c $< -o $@
函数:
-
$ ( wildcard PATTERN . . .)
- 功能:获取指定目录下指定类型的文件列表
- 参数:PATTERN:某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
- 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
示例:
$(wildcard *.c ./sub/*.c)
返回值格式:a.c b.c c.c d.c e.c f.c
-
$ (patsubst ,,
) - 功能:查找
中的单词(单词以"空格"、“Tab"或“回车""换行"分隔)是否符合
模式,如果匹配的话,则以替换。 - 可以包括通配符’%‘,表示任意长度的字串。如果中也包含%,那么中的这个 ‘%’ 将是中的那个号所代表的字串。(可以用’\‘来转义,以’%'来表示真实含义的
%
字符) - 返回:函数返回被替换过后的字符串
示例:
$ (patsubst %.c, %.o, x.c bar.c)
返回值格式:x.o bar.o
- 功能:查找
# define varience
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
$(CC) $(objs) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
clean:
rm $(objs) -f
注意:
- clean不会通过 make命令就自动执行,一般默认只执行第一条规则,其它规则一般都是为第一条规则服务的。所以如果需要执行其他规则需要手动进行执行。
boyangcao@MyLinux:~/Linux/Lesson07$ make clean
rm ./add.o ./mult.o ./main.o ./div.o ./sub.o -f
- 如果如下执行,会出现这样的情况:
boyangcao@MyLinux:~/Linux/Lesson07$ touch clean
boyangcao@MyLinux:~/Linux/Lesson07$ make clean
make: “clean”已是最新。
这是因为clean: 后面没有任何依赖,那么目标 总是被认为是更新的,所以不会再执行该规则中的命令。
解决办法:
# 设置clean为伪目标
.PHONY:clean
clean:
rm $(objs) -f
GDB调试
GDB是由GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境,GDB是 Linux和许多类Unix系统中的标准开发环境。
GDB 完成四个方面的功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个BUG产生的影响修正从而测试其他 BUG
准备工作:
为调试而编译时,我们会()关掉编译器的优化选项(-o
),并打开调试选项(-g
)。另外, `-wall`在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG。
gcc -g -Wall program.c -o program
‘-g’ :在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。
GDB命令-启动、退出、查看代码
注意:
- 执行finish的时候,后续执行当前函数的内容中不能有断点。
- 执行until,后续执行当前循环的内容中不能有断点。