1. GCC介绍
1.1 GCC的历史
GNU C Compiler(GCC)的作者是Richard Stallman,他是GNU项目的负责人,GCC是Linux的唯一编译器。GNU的意思是“GNU is Not Unix ”。
GNU项目是在1984年开始的,其目的是创建一个免费的Unix-like操作系统。每一个Unix-like操作系统都需要一个C编译器,当时没有免费的C编译器,于是GNU项目开始开发一个免费的C编译器,即“GCC”。在1987年,第一个可移植的免费的C编译器——GCC发布。
1.2 GCC的特点
GCC是一个可移植的编译器——它可以在大多数平台上运行;
GCC不仅仅是一个本地的编译器——可以从一个平台编译出另外一个平台的软件;
GCC可以自由的编辑和修改——任何人都有使用和修改GCC的自由。
1.3 C/C++的特点
C和C++允许直接访问计算机的内存。因此他们通常被用于编写底层的系统软件。但是要确保对内存的正确访问。
2. GCC编译(Linux环境)
2.1 编译一个简单的C程序
// hello.c源码
#include <stdio.h>
int main(){
printf("Hello, world!\n");
return 0;
}
编译运行这个hello.c程序,生成可执行文件hello
$ gcc -Wall hello.c -o hello
其中,-Wall表示-Warning all,即发现潜在错误会给出警告。
$ ./hello
./hello运行hello文件,输出"Hello, world!"
2.2 编译多个源文件
编译hello.c和main.c文件,其中main.c是主函数,hello.h声明函数原型,hello.c提供函数原型的定义。
// hello.c
#include <stdio.h>
#include "hello.h"
void hello(const char* stting){
printf(string);
}
// main.c
#include <stdio.h>
#include "hello.h"
int main(){
hello("Hello, World!\n");
return 0;
}
// hello.h
void hello(const char* string);
$ gcc -Wall main.c hello.c -o newhello
注意:hello.h不需要放在命令行里,GCC分析到#include "hello.h"能自己找到该头文件。
"FILE.h"在当前文件目录下找,找不到就去系统头文件目录找;
<FILE.h>直接去系统头文件目录下找。
系统头文件一般在/usr/include或/usr/local/include目录下。
执行gcc命令后,输出结果。
2.3 冗余编译选项,独立编译和链接顺序
(1)冗余编译选项 -v
$ gcc -v -Wall hello.c
"-v"选项能被用于详细陈述编译和链接的详细信息。
(2)独立编译和链接顺序
如果一个程序被存储在一个文件中,如果这个程序有一点改变,则需要重新编译整个文件,重新编译十分耗时。因此,我们可以将一个原文件分为多个文件进行单独编译,这样当某个文件有改动时,只需要单独对这个文件进行编译即可。分为两个阶段:
第一个阶段:将多个文件分别编译成.o目标文件
第二个阶段:将.o目标文件用linker链接器将各个.o目标文件链接起来,来形成一个可执行文件。
① 将.c文件编译成.o目标文件;
$ gcc -Wall -c main.c hello.c
结果会生成main.o文件和hello.o文件。
② 将.o文件链接;
$ gcc main.o hello.o -o hello
将main.o文件和hello.o文件链接成可执行文件hello。
注意:main.o和hello.o的顺序,现在大部分编译器不考虑.o文件的顺序,但是在古老的编译器,要把main.o放在前面,hello.o放在后面,英文main函数要调用hello函数。
③ 执行hello。
3. 重编译重链接、链接外部库
3.1 重编译重链接
通常来说,链接比编译更快——在一个有许多源文件的大型项目中,只重新编译那些被修改的源文件是一个很大的节约。
重新编译只被改动过的源文件可以被GNU Make自动的执行。
3.2 链接外部库
一系列预先编译好的.o文件组合起来形成一个库文件。静态链接库文件在Linux是.a文件,在Windows是.lib文件,库文件通过GNU archiver(ar)来创建打包。
为了确保编译器能够链接到外部库,我们需要将库应用到gcc命令行:
$ gcc -Wall main.c /usr/lib/libm.a -o calc
即编译main.c文件成可执行文件calc,其中main.c中引用的库从/usr/lib/libm.a中寻找。
为了避免外部库路径过长,编译器提供了'-l'的短接命令命令:
$ gcc -Wall main.c -lm -o calc
编译选项'-lNAME'将尝试用库文件'libNAME.a'来链接目标文件,这个库将在标准库(standard library)寻找。
对于使用一个静态库,必须要包含正确的头文件,因为头文件包含了函数所需要的原型以及对应的参数和返回类型。
当额外的库被安装到其他目录,就有必要扩大搜索路径,为了这些库能被找到。“-I”和“-L”参数,其中-I表示在-I路径下寻找头文件,-L表示在-L路径下寻找需要链接的库文件。注意:在#inlcude " xxx"不要加绝对路径的信息,这样到别的平台运行容易报错。采用相对路径合理。
4. ar创建库、搜索路径
4.1 使用ar创建一个库文件
库文件=多个.o目标文件融合在一起
你可以使用下面的命令来创建一个静态库文件:
$ ar cr libNAME.a file1.o file2.o ... filen.o
ar表示创建库文件,cr表示创建(create)和替换(replace)
ar还可以查看一个库文件里面有多少个目标文件。
$ ar t libNAME.a
4.2 使用ar创建库文件例子
当前目录下有以下四个文件:mylib.h func1.c func2.c main.c
// mylib.h
int func1(int x, int y);
void func2(int x);
// func1.c
# include "mylib.h"
int func1(int x, int y){
return (x + y);
}
// func2.c
#include <stdio.h>
#include "mylib.h"
void func2(int x){
printf("The result is %d\n", x);
}
// main.c
#include <stdio.h>
#include "mylib.h"
int main(){
int i;
i = func1(1, 2);
func2(i);
return 0;
}
(1)使用gcc将func1.c 和 func2.c将源文件编译成func1.o和func2.o目标文件。
$ gcc -Wall -c func1.c func2.c
结果如下图:
(2)将func1.o和func2.o融合成库文件libhello.a
$ ar cr libhello.a func1.o func2.o
结果如下图:
(3)查询libhello.a包括哪些.o目标文件
$ ar -t libhello.a
(4)使用main.c和库文件libhello.a编译程序生成可执行文件hello
方法一:
$ gcc -Wall main.c libhello.a -o hello
注意:main.c 和libhello.a的顺序不能颠倒!!!
方法二:
此时如果我们使用短接命令-l
$ gcc -Wall main.c -lhello -o h2
提示报错:
这是因为-l命令不是从当前目录找libhello.a, 而是从系统库文件目录找libhello.a
我们将libhello.a文件复制到系统库目录/usr/lib下
$ sudo cp libhello.a /usr/lib
因为要拷贝到系统目录,需要sudo执行
再次执行
$ gcc -Wall main.c -lhello -o h2
显示生成可执行文件h2:
(5)运行hello文件
4.3 -L命令的使用
-L能够制定库文件搜索路径
方法一:相对路径
$ gcc -Wall main.c -L. -lhello -o h3
-L.表示在当前目录下寻找库文件,-lhello表示要寻找的库文件名为libhello.a
方法二:绝对路径
$ gcc -Wall main.c -L/home/gtc/Desktop/code/v2 -lhello -o h4
4.4 程序文件结构划分main.c、lib文件和include文件
include文件夹保存头文件,lib文件夹下放库文件
$ gcc -Wall -Iinclude -L./lib main.c -lhello -o hello
其中-Iinclude表示在当前目录下寻找include头文件
-L./lib表示在当前目录下寻找库文件
-lhello表示要寻找的库文件名字为: libhello.a
5. 静、动态链接库
外部库通常有两种形式:静态库(static libraries)和动态库(shared libraries), 静态库是.a文件(Linux),.lib(Windows)。在静态库中,凡是调用到的函数,都会被拷贝到目标文件里面。
缺点:如果多个程序同时调用同一个静态库,会将同样的库重复复制多份到机器码中,造成内存过大。
动态库采用了一种更高级的链接形式,可以使得最终的可执行文件更小。动态库的文件后缀是.so(shared object)。windows中动态库的后缀是.dll
Linux | Windows | |
静态库 | .a | .lib |
动态库 | .so | .dll |
动态库链接的时候,不是把机器码拷贝到可执行文件里,而是拷贝一个地址(指针)到可执行文件,因此多个程序调用同一个动态库时,只用将一个动态库加载进内存即可,能够节约内存。
可执行文件在运行之前,动态库的机器码才会被加载进内存,并被多个程序共享使用,这种方式叫动态链接(dynamic linking)。