声明: 本篇博客的学习途径主要为以下网站和课堂讲解,发博客目的仅为学习使用
http://c.biancheng.net/gcc/
gcc和g++的区别
当下的 GCC 编译器还支持编译 Go、Objective-C,Objective-C ++等等
通过执行 gcc 或者 g++ 指令来调用 GCC 编译器。
通俗习惯:使用 gcc 指令编译 C 语言程序,用 g++ 指令编译 C++ 代码
具体区别:
- 只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译,gcc会自动判断
- xxx.c:默认以编译 C 语言程序的方式编译此文件;
- xxx.cpp:默认以编译 C++ 程序的方式编译此文件。
- xxx.m:默认以编译 Objective-C 程序的方式编译此文件;
- xxx.go:默认以编译 Go 语言程序的方式编译此文件;
- 使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件
注意 : 很多 C++ 程序都会调用某些标准库中现有的函数或者类对象,而单纯的 gcc 命令是无法自动链接这些标准库文件的
gcc一步编译
gcc一步编译,没有-o 默认生成.out
[root@bogon ~]# gcc demo.c
[root@bogon ~]# ls
a.out demo.c
[root@bogon ~]# ./a.out
GCC教程:http://c.biancheng.net/gcc/
gcc 用户使用-o指定生成某阶段文件,这里指定生成.out(.exe)文件
[root@bogon ~]# gcc demo.c -o demo.exe
- -E 预处理
- -S 只编译,不汇编
- -c 只编译不链接:产生.o文件,就是obj文件,不产生执行文件(c : compile 编写)。
- -o 指定输出文件名(o:output)-o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。
- -g 可执行程序包含调试信息:加个-g 是为了gdb 用,不然gdb用不到。
【注意】-E -S -c -o除了可以从.c一步编译到目标,也可以分步执行。
demo.i — -S —> demo.s √
从源代码到可执行文件
“编译”: 将代码转变为可执行程序,期间经历四个步骤
- 预处理(Preprocessing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
预处理(Preprocessing)
预处理过程主要是处理那些源文件和头文件中以#开头的命令,比如 #include、#define、#ifdef 等。
- 将所有的#define删除,并展开所有的宏定义。
- 处理所有条件编译命令,比如 #if、#ifdef、#elif、#else、#endif 等。
- 处理#include命令,将被包含文件的内容插入到该命令所在的位置,这与复制粘贴的效果一样。注意,这个过程是递归进行的,也就是说被包含的文件可能还会包含其他的文件。
- 删除所有的注释//和/* … */。
- 添加行号和文件名标识,便于在调试和出错时给出具体的代码位置。
- 保留所有的#pragma命令,因为编译器需要使用它们。
.i文件:宏被展开的源文件,所有文件都被插入。
当你无法判断宏定义是否正确,或者文件包含是否有效时,可以查看.i文件来确定问题。
$gcc -E demo.c -o demo.i
-E 表示只进行预处理编译
GCC -E选项:对源程序做预处理操作
其中,通过为 gcc 指令添加 -E 选项,即可控制 GCC 编译器仅对源代码做预处理操作。
默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。
[root@bogon demo]# gcc -E demo.c
因此该指令往往会和 -o 选项连用,将结果导入到指令的文件中。
[root@bogon demo]# gcc -E demo.c -o demo.i
[root@bogon demo]# ls
demo.c demo.i
可以通过执行cat demo.i指令查看该文件中的内容
可以为 gcc 指令再添加一个 -C (大写)选项,阻止 GCC 删除源文件和头文件中的注释:
[root@bogon demo]# gcc -E -C demo.c -o demo.i
编译(Compilation)
编译就是把预处理完的文件进行一些列的词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。
具体过程:参考《编译原理》
大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令。
$gcc -S demo.i -o demo.s
$gcc -S demo.c -o demo.s
GCC -S选项:编译非汇编文件
所谓编译,简单理解就是将预处理得到的程序代码,经过一系列的词法分析、语法分析、语义分析以及优化,加工为当前机器支持的汇编代码。
给 gcc 指令添加 -S(注意是大写)选项,即可令 GCC 编译器仅将指定文件加工至编译阶段,并生成对应的汇编代码文件。
[root@bogon demo]# gcc -S demo.i
[root@bogon demo]# ls
demo.c demo.i demo.s
还可以为 gcc -S 指令添加 -o 选项,令 GCC 编译器将编译结果保存在我们指定的文件中。
[root@bogon demo]# gcc -S demo.i -o test.s
[root@bogon demo]# ls
demo.c demo.i demo.s test.s
gcc -S 指令操作的文件并非必须是经过预处理后得到的 .i 文件,-S 选项的功能是令 GCC 编译器将指定文件处理至编译阶段结束。这也就意味着,gcc -S 指令可以操作预处理后的 .i 文件
- 如果操作对象为 .i 文件,则 GCC 编译器只需编译此文件;
- 如果操作对象为 .c 或者 .cpp 源代码文件,则 GCC 编译器会对其进行预处理和编译这 2 步操作。
如果要读.s汇编文件的话,可以增加 -fverbose-asm ,GCC 编译器会自行为汇编代码添加必要的注释 , 增加可读性
[root@bogon demo]# gcc -S demo.c -fverbose-asm
汇编(Assembly):翻译
将汇编代码转换成可以执行的机器指令
只是根据汇编语句和机器指令的对照表一一翻译就可以了。
在 GCC 下的后缀为.o,生成二进制文件
GCC -c选项:生成目标文件
汇编过程会简单很多,它并没有复杂的语法,也没有语义,也不需要做指令优化,只需要根据汇编语句和机器指令的对照表一一翻译即可。
[root@bogon demo]# gcc -c demo.s
[root@bogon demo]# ls
demo.c demo.i demo.o demo.s
该指令生成了和 demo.s 同名但后缀名为 .o 的文件,这就是经过汇编操作得到的目标文件。
同理 也可以添加一个-o选项,存储结果
[root@bogon demo]# gcc -c demo.s -o test.o
[root@bogon demo]# ls
demo.c demo.s test.o
需要强调的一点是,和 gcc -S 类似,gcc -c 选项并非只能用于加工 .s 文件。
- 如果指定文件为源程序文件(例如 demo.c),则 gcc -c 指令会对 demo.c 文件执行预处理、编译以及汇编这 3 步操作;
- 如果指定文件为刚刚经过预处理后的文件(例如 demo.i),则 gcc -c 指令对 demo.i 文件执行编译和汇编这 2 步操作;
- 如果指定文件为刚刚经过编译后的文件(例如 demo.s),则 gcc -c 指令只对 demo.s 文件执行汇编这 1 步操作。
链接(Linking)
目标文件已经是二进制文件,只是有些函数和全局变量的地址还未找到,程序不能执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。
gcc 指令
得到生成目标文件之后,接下来就可以直接使用 gcc 指令继续执行链接操作
√[root@bogon demo]# gcc demo.o -o democ.exe
[root@bogon demo]# ./demo.exe
gcc 会根据所给文件的后缀名 .o,自行判断出此类文件为目标文件
其他选项
GCC -o选项:指定输出文件(输出结果保存在我们指定的文件)
添加 -o 选项,令 GCC 编译器将编译结果保存在我们指定的文件中。
[root@bogon demo]# gcc [-E|-S|-c] [infile] [-o outfile]
- [infile] 表示输入文件(也即要处理的文件),它可以是源文件、汇编文件或者目标文件;
- [outfile] 表示输出文件(也即处理的结果),可以是预处理文件、目标文件、可执行文件等。
GCC -l (小写L)选项:手动添加链接库
- 标准库的大部分函数通常放在文件 libc.a 中(文件名后缀.a代表“achieve”,译为“获取”),
- 或者放在用于共享的动态链接文件 libc.so 中(文件名后缀.so代表“share object”,译为“共享对象”)。
这些链接库一般位于 /lib/ 或 /usr/lib/,或者位于 GCC 默认搜索的其他目录。
当使用 GCC 编译和链接程序时,GCC 默认会链接 libc.a 或者 libc.so,但是对于其他的库(例如非标准库、第三方库等),就需要手动添加。
【注意:】标准头文件 <math.h> 对应的数学库默认也不会被链接,如果没有手动将它添加进来,就会发生函数未定义错误。
手动添加标准库:math.h函数库为例
【举例】:
编写一个数学程序 main.c,并使用到了 cos() 函数,它位于 <math.h> 头文件。<math.h> 头文件默认不会被链接
//main.c
#include <stdio.h> /* printf */
#include <math.h> /* cos */
#define PI 3.14159265
int main ()
{
double param, result;
param = 60.0;
result = cos ( param * PI / 180.0 );
printf ("The cosine of %f degrees is %f.\n", param, result );
return 0;
}
首先,数学库存在于 /usr/lib之类的路径下
数学库的文件名是 libm.a
libname.a :lib 和.a 都是规定的
只有name是基本名称,因为数学库的文件名是libm.a,所以name=m
-lname
本例中,基本名称name为 m。
[root@bogon demo]# gcc main.c -o main.out -lm
链接其它目录中的库
如果想链接其它目录中的库,就得特别指明
有三种方式可以链接在 GCC 搜索路径以外的链接库:
例如,如果链接库名为 libm.a,并且位于 /usr/lib 目录,那么下面的命令会让 GCC 编译 main.c,然后将 libm.a 链接到 main.o
- 方法一:把链接库作为一般的目标文件,为 GCC 指定该链接库的完整路径与文件名。(直接添加路径)
[root@bogon demo]# gcc main.c -o main.out /usr/lib/libm.a
- 方法二:使用-L选项,为 GCC 增加另一个搜索链接库的目录:(大写-L添加库路径 小写-l链接库)
[root@bogon demo]# gcc main.c -o main.out -L/usr/lib -lm
[root@bogon demo]# gcc main.c -o main.out -L/usr/lib -L/lib -lm
可以使用多个-L选项,或者在一个-L选项内使用冒号分割的路径列表。
- 方法三:把包括所需链接库的目录加到环境变量 LIBRARYPATH 中。
头文件和库文件
- 头文件只存储变量、函数或者类等这些功能模块的声明部分,
- 库文件才负责存储各模块具体的实现部分。
∴ 库文件是无法直接使用的,只能通过头文件间接调用。
头文件和库文件相结合的访问机制,最大的好处在于,有时候我们只想让别人使用自己实现的功能,并不想公开实现功能的源码,就可以将其制作为库文件,这样用户获取到的是二进制文件,而头文件又只包含声明部分,这样就实现了“将源码隐藏起来”的目的,且不会影响用户使用
库文件用于程序的链接阶段
总结:
- 默认情况下会优先使用动态链接库实现链接操作
- 在 Linux 发行版中,静态链接库和动态链接库通常存放在 /usr/bin 或者 /bin 目录下
动态链接库
静态链接库实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,GCC 编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。
- 优势是,生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强);
- 劣势是,如果程序文件中多次调用库中的同一功能模块,则该模块代码势必就会被复制多次,生成的可执行文件中会包含多段完全相同的代码,造成代码的冗余。
和使用动态链接库生成的可执行文件相比,静态链接库生成的可执行文件的体积更大。
在 Linux 发行版系统中,静态链接库文件的后缀名通常用 .a 表示
在 Windows 系统中,静态链接库文件的后缀名为 .lib。
动态链接库(共享链接库)
程序文件中哪里需要库文件的功能模块,GCC 编译器不会直接将该功能模块的代码拷贝到文件中,而是将功能模块的位置信息记录到文件中,直接生成可执行文件。
这样生成的可执行文件是无法独立运行的。
采用动态链接库生成的可执行文件运行时,GCC 编译器会将对应的动态链接库一同加载在内存中,由于可执行文件中事先记录了所需功能模块的位置信息,所以在现有动态链接库的支持下,也可以成功运行。
- 优势是,由于可执行文件中记录的是功能模块的地址,真正的实现代码会在程序运行时被载入内存,这意味着,即便功能模块被调用多次,使用的都是同一份实现代码(这也是将动态链接库称为共享链接库的原因)。
- 劣势是,此方式生成的可执行文件无法独立运行,必须借助相应的库文件(可移植性差)。
和使用静态链接库生成的可执行文件相比,动态链接库生成的可执行文件的体积更小,因为其内部不会被复制一堆冗余的代码。
在 Linux 发行版系统中,动态链接库的后缀名通常用 .so 表示;
在 Windows 系统中,动态链接库的后缀名为 .dll。
创建链接库
【前景铺垫】
[root@bogon demo]# ls <- demo 目录结构
func1.c func2.c main.c myhead.h
[root@bogon demo]# cat myhead.h <- myhead.h 文件内容
#ifndef __TEST_H_
#define __TEST_H_
int func1(int a,int b);
int func2(int a,int b);
#endif
[root@bogon demo]# cat func1.c <- func1.c 文件内容
#include "myhead.h"
int add(int a,int b)
{
return a + b;
}
[root@bogon demo]# cat func2.c <- func2.c 文件内容
#include "myhead.h"
int sub(int a,int b)
{
return a - b;
}
[root@bogon demo]# cat main.c <- main.c 文件内容
#include <stdio.h>
#include "test.h" //必须引入头文件
int main(void)
{
int m, n;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, func1(m, n));
printf("%d-%d=%d\n", m, n, func2(m, n));
return 0;
}
静态链接库
制作静态链接库
如果仅希望别人使用我们实现的功能,但又不想它看到具体实现的源码,该怎么办呢?很简单,就是将它们加工成一个静态链接库。
条件:
- 源文件中只提供可以重复使用的代码,例如函数、设计好的类等,不能包含 main 主函数;
- 源文件在实现具备模块功能的同时,还要提供访问它的接口,也就是包含各个功能模块声明部分的头文件。
【前景提要】:现在有func1.c func2.c func3.c main.c myhead.h 合计五个文件
- 将所有指定的源文件,都编译成相应的目标文件:
[root@bogon demo]# gcc -c func1.c func2.c func3.c
[root@bogon demo]# ls
func1.c func1.o func2.c func2.o func3.c func3.o main.c myhead.h
- 然后使用 ar 压缩指令,将生成的目标文件打包成静态链接库,其基本格式如下:
ar rcs 静态链接库名称 目标文件1 目标文件2 ...
静态链接库起名规则如下:
libxxx.a
英文:库(library)
其中,xxx 代指我们为该库起的名字,比如 Linux 系统自带的一些静态链接库名称为 libc.a、libgcc.a、libm.a,它们的名称分别为 c、gcc 和 m。
具体实践
[root@bogon demo]# ar rcs libmy.a func1.o func2.o func3.o
[root@bogon demo]# ls
func1.c func1.o func2.c func2.o func3.c func3.o main.c myhead.h libmy.a
使用静态链接库
静态链接库的使用:在程序的链接阶段,将静态链接库和其他目标文件 一起执行链接操作,从而生成可执行文件。
1)我们将 main.c 文件编译为目标文件:
[root@bogon demo]# gcc -c main.c
[root@bogon demo]# ls
libmy.a main.o myhead.h main.c
2)在链接阶段, -static 选项强制 GCC 编译器使用静态链接库。
[root@bogon demo]# gcc -static main.o libmy.a
[root@bogon demo]# ls
libmy.a main.o myhead.h main.c a.out
【注意】如果 GCC 编译器提示无法找到 libmy.a,还可以使用如下方式完成链接操作:
[root@bogon demo]# gcc main.o -static -L /root/demo/ -lmy
[root@bogon demo]# ls
a.out main.c myhead.h libmy.a main.o a.out
cat a.out的时候是乱码,但是依旧可以运行,目标达成 √
动态链接库
创建动态链接库
总的来说,动态链接库的创建方式有 2 种。
1)方法一 : 直接使用源文件创建动态链接库,采用 gcc 命令实现的基本格式如下:
gcc -fpic -shared 源文件名... -o 动态链接库名
- -shared 选项用于强制生成动态链接库;
- -fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。
[root@bogon demo]# ls
func1.c func2.c main.c myhead.h
[root@bogon demo]# gcc -fpic -shared func1.c func2.c -o libmy.so
[root@bogon demo]# ls
func1.c func2.c main.c myhead.h libmy.so
注意,动态链接库的命令规则和静态链接库完全相同,只不过在 Linux 发行版系统中,其后缀名用 .so 表示;Windows 系统中,后缀名为 .dll。
1)方法二 :先使用 gcc -c 指令将指定源文件编译为目标文件
[root@bogon demo]# ls
func1.c func2.c main.c myhead.h
[root@bogon demo]# gcc -c -fpic func1.c func2.c
[root@bogon demo]# ls
func1.c func2.c main.c myhead.h func1.o func2.o main.o
接下来利用上一步生成的目标文件,生成动态链接库
[root@bogon demo]# gcc -shared func1.o func2.o -o libmy.so
[root@bogon demo]# ls
func1.c func2.c main.c myhead.h func1.o func2.o main.o libmy.so
使用动态链接库
动态链接库的使用场景就是和项目中其它源文件或目标文件一起参与链接。
注意,myhead.h 头文件并不直接参与编译,因为在程序的预处理阶段,已经对项目中需要用到的头文件做了处理。
[root@bogon demo]# gcc main.c libmy.so -o main.exe
[root@bogon demo]# ls
func1.c func2.c libmy.so main.c main.exe myhead.h
【注意】生成的 main.exe 通常无法直接执行,执行过程中无法找到 libmymath.so 动态链接库。
[root@bogon demo]# ./main.exe
./a.out: error while loading shared libraries: libd.so: cannot open shared object file: No such file or directory
通过执行ldd main.exe指令,可以查看当前文件在执行时需要用到的所有动态链接库,以及各个库文件的存储位置
ldd xxx.exe
[root@bogon demo]# ldd main.exe
linux-vdso.so.1 => (0x00007fff423ff000)
lib.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00000037e2c00000)
/lib64/ld-linux-x86-64.so.2 (0x00000037e2800000)
运行由动态链接库生成的可执行文件时,必须确保程序在运行时可以找到这个动态链接库。
常用的解决方案有如下几种:
- 将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
- 在终端输入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx,其中 xxx 为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效);
- 修改~/.bashrc 或~/.bash_profile 文件,即在文件最后一行添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx(xxx 为动态库文件的绝对存储路径)。保存之后,执行source .bashrc指令(此方式仅对当前登陆用户有效)。
本次有效的解决方案是:只需要将 libmymath.so 库文件移动 /usr/lib64 或者 /lib64 目录下,即可使 main.exe 成功执行:
[root@bogon demo]# ldd main.exe
linux-vdso.so.1 => (0x00007fff06fb3000)
libmymath.so => /lib64/libmymath.so (0x00007f65b2a62000)
libc.so.6 => /lib64/libc.so.6 (0x00000037e2c00000)
/lib64/ld-linux-x86-64.so.2 (0x00000037e2800000)
[root@bogon demo]# ./main.exe
Input two numbers: 10 2
10+2=12
10-2=8