gcc 它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。
我们的程序只有一个源文件时,直接就可以用gcc命令编译它。
可是,如果我们的程序包含很多个源文件时,该咋整?
用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了make工具。
1. 了解编译 4 个步骤
1.将C语言源程序预处理,生成.i文件。 #(1)将#include 的头文件展开:#include本质上是代码的拷贝动作,所有inlclude的头文件的内容在展开阶段均会被直接复制过来。
#(2)将#define语句指定的值转换为变量。
#(3)将宏定义转换为具体代码。
#(4)根据#if #elif 和#endif指定的位置包含或排除特定部分的代码。
2.预处理后的.i文件编译成为汇编语言,生成.s文件。 #基于预处理阶段得到的.i文件进行一系列的词法分析、语法分析、语义分析和相关优化后生成汇编文件
3.将汇编语言文件经过汇编,生成目标文件.o文件。 #将汇编文件转换为机器可以执行的指令,也就是机器码。
#在linux下,.o 是ELF格式的。一个cpp文件对应一个编译单元,即对应一个.o目标文件,
#需要知道,目标文件里面除了包含了编译后的机器指令代码、数据,还包括及 '符号表' 等链接需要的信息,
#它从格式上已经非常接近可执行文件,只需要再进行链接处理即可成为最终的可执行文件或者库。
4.将各个模块的.o文件链接起来生成一个可执行程序文件。#链接由链接器完成
#如示例hello.cpp文件中调用了printf函数,目标文件hello.o中引用的printf '符号' 还未解析,
#同时也缺少系统运行库libc和相关的启动文件,只有这些都链接在一起,才能产出最终的可执行程序
图示如下:
1.2 了解链接器的符号
编译中的概念。
它也是链接过程中的核心元素,可以说链接就是根据符号来的。即编译四步中的最后一步。
在链接中,
目标文件之间的连接实际上是目标文件直接对地址的引用,也就是对函数和变量的地址的引用。
例:
我们生成的hello.o就引用了其他.o文件中定义的printf,除了函数,变量也是类似,每个函数或变量都有自己独特的名字,
才能避免链接过程中不同变量和函数的混淆,编译器有一套生成规则,具体的可以用nm命令去查看符号。
在链接中, 我们将函数和变量统称为符号(Symbol)
,函数名或者变量名就是符号名(Symbol Name)
。
每个目标文件都有相应的符号表,记录了目标文件所用到的所有的符号,每个定义的符号由一个对应的值,叫做符号值,
对于变量和函数来说,符号值就是他们的地址
。
2. Common Commands
2.1 生成可执行文件
-o 指定生成文件的名字 #
-E 预处理 #头文件是在这个地方用到的 参考链接http://c.biancheng.net/view/7971.html
-S 编译 #
-c 汇编 #得到的是二进制文件
无参数 链接 #gcc 会自动执行这一过程 链接静态库或动态库 参考链接http://c.biancheng.net/view/2382.html
#链接的工作就是将同一项目中各源文件生成的目标文件以及程序中用到的库文件整合为一个可执行文件
1.分 4 步编译(1节图示)例子:
gcc -E hello.c -o hello.i #不指定-o就不生成.i文件 #头文件是在这个地方用到的
gcc -S hello.i (-o hello.s) #可不指定-o参数,会自动生成相应.s文件
gcc -c hello.s (-o hello.o) #可不指定-o参数,会自动生成相应.o文件
gcc hello.o (-o hello.out) #可不指定-o参数,会自动生成相应.out文件,链接过程无参数
2.一步编译( 合并了 3 步过程)
gcc -c hello.c #会自动生成 hello.out 文件
3.真-一步编译
gcc hello.c -o hello -llibrary
注意:
一步编译也生成了 分步编译中产生的文件,不过gcc默认将临时文件删除了,也可以使用 gcc 选项保留那些临时文件。
./hello.out #执行文件
./hello #执行文件
2.2 编译常用选项
2.2.1 链接库
-lLIBRARY #小写L,手动指定链接时搜索指定的函数库LIBRARY ,可连接静态库或动态库
# linux默认对库的连接是使用动态库,在找不到动态库的情况下再选择静态库。
# GCC会自动在默认标准库目录(一般为/lib/ 或 /usr/lib/)中搜索文件
#参考链接 http://c.biancheng.net/view/2382.html
#静态库前缀 lib 和后缀 .a 是标准的,如 数学库的文件名是 libm.a,m是基本名称
#动态库前缀 lib 和后缀 .so是标准的,如
#-l 选项后紧跟着的就是 基本名称
例:
gcc main.c -o main.out -lm #链接数学库
-LDIRECTORY #指定链接额外的函数库搜索路径DIRECTORY
#为 GCC 增加另一个搜索链接库的目录(若不指定就只在默认标准库中搜索)
#参考链接 http://c.biancheng.net/view/2382.html
#一般配合 -l 选项一起使用
# 可以使用多个-L选项,或者在一个-L选项内使用冒号分割的路径列表。
例:
/Desktop下有个静态库 libmylib.a 被引用,故编译语句如下:
gcc main.c -o main -L/Desktop -lmylib
-r
无选项直接用库名链接库
例:
如果链接库名为 libm.a,并且位于 /usr/lib 目录,那么下面的命令会让 GCC 编译 main.c,然后将 libm.a 链接到 main.o:
gcc main.c -o main.out /usr/lib/libm.a
-static #指定链接为静态库
例:
如果当前目录有两个库libtestlib.so libtestlib.a,指定静态库
gcc test.cpp -L. -static -ltestlib
-Wl,-Bstatic #指示跟在后面的-lxxx选项链接的都是静态库
-Wl,-Bdynamic #指示跟在后面的-lxxx选项链接的都是动态库
例:
链接动态库 libtestdll.so 链接静态库 libtestlib.a , 最后的 -Wl,-Bdynamic 表示将缺省库链接模式恢复成动态链接
gcc test.cpp -L. -Wl,-Bdynamic -ltestdll -Wl,-Bstatic -ltestlib -Wl,-Bdynamic
-Wl,-R #指定运行时动态库路径
例:
编译生成main可执行程序时,
链接静态库 路径../static ,库名libmultidiv.a
链接动态库路径./ ,动态库名 libadd_minus.so
指定运行时动态库路径./ #运行时会在这个路径下直接按链接指定的动态库名进行查找,如此处就是在./下找 libadd_minus.so
gcc main.c -o main -L../static -lmulti_div -L./ -ladd_minus -I../static -Wl,-R./
2.2.2 Other
-I #指定头文件所在目录位置
①gcc编译时如果头文件和.c文件在同一个文件夹下通常不将头文件编译进去
例:gcc hello.c -o hello #hello.c引用了hello.h
②gcc编译时如果头文件和.c文件不在同一个文件夹下要指定头文件位置
例:gcc -I ./inc hello.c -o hello #hello.h在inc文件夹下
gcc hello.c -o hello -I./inc #hello.h在inc文件夹下,且-I的位置可以调整
-g #用于GDB调试,编译文件会比不加这个参数生成的文件大。
-Wall #生成所有警告信息
-D #向程序中动态注册宏定义
例:
gcc hello.c -D HELLO #动态注册宏HELLO
-v #查看版本
-l #表示程序指定的链接库名,如: -lm (math数学库) -lpthread 线程库
-L #添加库文件寻找路径, 如 -L /home/hello/lib表示将/home/hello/lib目录作为第一个寻找库文件的目录,-》寻找的顺序是:/home/hello/lib-->/lib-->/usr/lib-->/usr/local/lib
-static #指定查找的是静态库,常配置 -l 使用,指定链接的是 libname.a文件,而不是 libname.so文件
3. GCC 制作库
3.1 静态库
3.1.1 基础
以静态文件的方式加载,
若100M的静态库,有10个应用程序使用,那就需要100M*10=1000M的空间。
相应的每个应用程序占用内存大小就是:100M+程序本身大小
原理上来讲静态加载(调用)快。
编译后文件大小:
=源文件大小+静态库大小 (不一定相等,就是这个意思)
常用命令
file 静态库文件名 #可以查看静态库文件的信息
3.1.2 静态库的制作
1.将.c 文件汇编 为 .o 文件
gcc -c add_minus.c -o add_minus.o
2.利用 .o 文件生成静态库(必须是汇编文件.o 生成)
ar rc libadd_minus.a add_minus.o
#注意:生成静态库时利用的是源文件,而引用静态库的程序引用的却是 头文件。
# 故下面链接静态库编译时需指定 静态库对应头文件的路径
3.一步编译并链接静态库
gcc 源文件.c lib库名.a -o 生成文件名 -I .h文件所在路径
例:
gcc main.c libadd_minus.a -o main -I ./
或
gcc main.c -o main -L. -ladd_minus -I ./
4.补充:
补充1:
引用函数时,如果没有函数声明,编译时会自动生成隐式声明,
但编译器隐式声明都是int 函数名(参数列表)的固定格式(返回值一定是int型,函数名和参数列表会自动识别对应),
如果调用某个没有显示声明的函数且不满足这个隐式声明那么就会报错。
补充2:
库名.h的书写格式。可以避免多次重复声明或引用。
例:
#ifndef flag
#define flag
int add(int a,int b);
int sub(int a,int b);
#endif
3.2 动态库(共享库)
3.2.1 基础
若100M的动态库,有10个应用程序使用,因为是动态加载故只需要100M的空间。
相应的每个应用程序占用大小就是:程序本身大小。
原理上来讲动态加载(调用)较慢。
适用条件:
对时间要求低,而对空间要求高的程序中。
动态库常用命令:
ldd 文件名.out #可以查看链接的动态库的路径
#如果没有链接上动态库就会显示 not found
file 动态库文件名 #可以查看动态库文件的信息
3.2.2 动态库的制作
步骤:
1.将 .c 生成 .o 文件。
gcc -c add.c -o add.o -fPIC #-fPIC 表示生成与内存位置无关的代码
2.使用gcc -shared 制作动态库
gcc -shared lib库名.so add.o file2.o file3.o
或
gcc add.o file2.o file3.o -shared lib库名.so
3.1 编译生成可执行程序时指定动态库及头文件路径,但不指定运行时动态库路径
#-l:指定库名(要去掉lib以及.so后缀) -L:指定库路径
gcc test.c -o a.out -l 库名 -L lib库名.so所在路径 -I../static
3.2 编译生成可执行程序时指定动态库及头文件路径,且指定运行时动态库路径(-Wl,-R)
gcc test.c -o a.out -l 库名 -L lib库名.so所在路径 -I../static -Wl,-Rlib库名.so所在路径
4.运行可执行程序
./a.out
error1:error while loading shared libraries: cannot open shared object file:No such file or directory
用下面动态库指定加载路径方法可解决此问题。
3.2.3 动态库加载问题
静态库及自身函数都是执行时就加在到内存中并拥有地址,
而动态库是调用时才会加载到内存中并拥有地址。
需要动态库的可执行程序运行时搜索动态库的路径先后顺序:
1.编译目标代码时指定的动态库搜索路径;
利用 gcc 选项 -Wl,-R (详见上文)
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
此环境变量修改有两种方式:
1.临时生效方式:
export LD_LIBRARY_PATH=动态库路径 #只在执行该语句的终端中生效
2.永久生效(配置环境变量):
1)vi ~/.bashrc #修改其中的 export LD_LIBRARY_PATH后的路径值
2)修改其中的 export LD_LIBRARY_PATH后的路径值 #建议使用绝对路径
3). .bashrc 或 source .bashrc 或重新打开终端 #使配置的环境变量生效
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;#配置后要运行 ldconfig命令才能生效
修改此路径方法入下 3 步:
1)sudo vi /etc/ld.so.conf
2)新起一行 写入动态库绝对路径 保存
3)sudo ldconfig -v #使配置生效。-v 可选 显示生效文件
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib;