一、什么是gcc
gcc的全称是GNU Compiler Collection,它是一个能够编译多种语言的编译器。最开始gcc是作为C语言的编译器(GNU C Compiler),现在除了c语言,还支持C++、java、Pascal等语言。gcc支持多种硬件平台.
在 Linux 系统中,你可能会看到多个版本的 GCC 工具链,包括 gcc
, gcc-ar
, gcc-nm
, 和 gcc-ranlib
,以及这些工具的特定版本后缀(如 -7
)。这些工具的存在有几个原因:
-
多版本共存:不同的项目可能需要不同版本的 GCC 来编译,以确保最佳性能、兼容性或利用新的编译器特性。Linux 系统允许多个版本的 GCC 共存,这样用户可以根据需要选择适合的版本。
-
不同工具的特定用途:
- gcc:这是 GNU 编译器集合的主编译器,用于编译 C、C++、Objective-C、Fortran、Ada 和 Go 等语言的代码。
- gcc-ar:这是一个用于创建、修改和提取归档文件(静态库)的程序。它是
ar
命令的一个包装器,可以在 LTO(链接时间优化)过程中更好地处理归档。 - gcc-nm:这个工具显示符号信息,它是标准的
nm
命令的包装器,特别用于处理 GCC 编译器生成的对象文件中的符号。 - gcc-ranlib:这是用于生成归档库索引的工具,它是标凈的
ranlib
命令的包装器,有助于链接器更快地查找库中的符号。
-
特定版本的工具链:
- 后缀(如
-7
)表示这些工具属于 GCC 的特定版本。例如,gcc-7
,gcc-ar-7
,gcc-nm-7
, 和gcc-ranlib-7
都是 GCC 版本 7 的组成部分。这些特定版本的工具在系统上同时存在是为了支持在同一系统上同时进行多个版本的开发。
- 后缀(如
-
系统更新与兼容性:随着新版本的发布,新工具链可能引入了改进的优化、更好的语言支持或新的编译器特性。然而,一些旧项目可能还是需要使用旧版本的编译器来保证兼容性。
这种安排给了开发者灵活性,使他们能够为不同的项目选择最合适的工具和版本,同时也保持了向后兼容性。在处理具体的构建和维护任务时,开发者可以根据需要选择使用特定版本的工具。
二、gcc的编译过程
gcc编译程序主要经过四个过程:
- 预处理(Pre-Processing)
- 编译 (Compiling)
- 汇编 (Assembling)
- 链接 (Linking)
预处理实际上是将头文件、宏进行展开。编译阶段,gcc调用不同语言的编译器,例如c语言调用编译器ccl。gcc实际上是个工具链,在编译程序的过程中调用不同的工具。汇编阶段,gcc调用汇编器进行汇编。链接过程会将程序所需要的目标文件进行链接成可执行文件。汇编器生成的是可重定位的目标文件,学过操作系统,我们知道,在源程序中地址是从0开始的,这是一个相对地址,而程序真正在内存中运行时的地址肯定不是从0开始的,而且在编写源代码的时候也不能知道程序的绝对地址,所以重定位能够将源代码的代码、变量等定位为内存具体地址。下面以一张图来表示这个过程,注意过程中文件的后缀变化,编译选项和这些后缀有关。
三、GCC常用选项
现在我们有源文件hello.c,下面是一些gcc的使用示例:
gcc -E hello.c -o hello.i 对hello.c文件进行预处理,生成了hello.i 文件
gcc -S hello.i -o hello.s 对预处理文件进行编译,生成了汇编文件
gcc -c hello.s -o hello.o 对汇编文件进行编译,生成了目标文件
gcc hello.o -o hello 对目标文件进行链接,生成可执行文件
gcc hello.c -o hello 直接编译链接成可执行目标文件
gcc -c hello.c 或 gcc -c hello.c -o hello.o 编译生成可重定位目标文件
使用gcc时可以加上-Wall选项。下面这个例子如果不加上-Wall选项,编译器不会报出任何错误或警告,但是程序的结果却不是预期的:
//bad.c
#include<stdio.h>
int main()
{printf("the number is %f ",5); //程序输出了the number is 0.000000,结果错误return 0;}
很明显这段代码printf打印时,5是整数不是浮点类型。
使用-Wall选项:
gcc -Wall bad.c -o bad
生成的二进制文件还是可执行的,但是不是我们需要的结果。
四、使用外部库
在使用C语言和其他语言进行程序设计的时候,我们需要头文件来提供对常数的定义和对系统及库函数调用的声明。库文件是一些预先编译好的函数集合,那些函数都是按照可重用原则编写的。它们通常由一组互相关联的可重用原则编写的,它们通常由一组互相关联的用来完成某项常见工作的函数构成。使用库的优点在于:
- 模块化的开发
- 可重用性
- 可维护性
库又可以分为静态库与动态库:
-
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。静态库比较占用磁盘空间,而且程序不可以共享静态库。运行时也是比较占内存的,因为每个程序都包含了一份静态库。
-
动态库(.so或.sa):程序在运行的时候才去链接共享库的代码,多个程序共享使用库的代码,这样就减少了程序的体积。
一般头文件或库文件的位置在:
- /usr/include及其子目录底下的include文件夹
- /usr/local/include及其子目录底下的include文件夹
- /usr/lib
- /usr/local/lib
- /lib
五 、静态库和动态库的优缺点
1.在Window中
.dll结尾的文件为动态库
.lib结尾的文件静态库
库即为源代码的二进制文件
2.在linux中
.so结尾的文件为动态库
.a结尾的文件为静态库
3.静态库和动态库的不同点
-
二者的不同点在于代码被载入的时刻不同。
-
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
-
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
4.库文件是如何产生的在Linux下
静态库的后缀是.a,它的产生分两步
Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表
Step 2.ar命令将很多.o转换成.a,成文静态库
动态库的后缀是.so,它由gcc加特定参数编译产生。
例如:
$ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname, libfoo.so.1 -olibfoo.so.1.0 *.
5.库文件是如何命名的,有没有什么规范
在linux下,库文件一般放在/usr/lib和/lib下,
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号
6.如何知道一个可执行程序依赖的共享库
ldd /path/to/your/executable
可以看到ln命令依赖于libc库和ld-linux库
五、静态库的制作实列
1.头文件mylib.h —— 声明静态库所导出的函数
#ifndef _mylib_H_ //如果没有定义此标识符,编码以下程序
#define _mylib_H_ void welcome();void outstring (const char * str);
#endif
(2) 对应于头文件的源文件mylib.c —— 实现静态库所导出的函数
#include "mylib.h"
#include <stdio.h>
void welcome() {printf ("welcome to libmylib\n");
}
void outstring(const char * str) {if (str !=NULL)printf ("%s",str);
}
(3)编译mylib.c生成目标文件:
# gcc –o mylib.o –c mylib.c
注: -c是只编译而不生成可执行文件,-o指定文件名
(4)将目标文件加入到静态库中,静态库为libmylib.a
#ar rcs libmylib.a mylib.o
参数说明:
/* r —在库中加入新成员文件,如果要加入的成员文件存在,则替换之默认情况下,新的成员文件增加在库的结尾处
/* c 创建一个库
在linux环境中, 使用ar命令创建静态库文件.如下是命令的选项:
d -----从指定的静态库文件中删除文件
m -----把文件移动到指定的静态库文件中
p -----把静态库文件中指定的文件输出到标准输出
q -----快速地把文件追加到静态库文件中
r -----把文件插入到静态库文件中
t -----显示静态库文件中文件的列表
x -----从静态库文件中提取文件
还有多个修饰符修改以上基本选项,详细请man ar 以下列出三个:
a -----把新的目标文件(*.o)添加到静态库文件中现有文件之后
b-----***************************************之前
v -----使用详细模式
ar 命令的命令行格式如下:
ar[-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files...
参数archive定义库的名称, files是库文件中包含的目标文件的清单, 用空格分隔每个文件.
/* s 无论ar命令是否修改了库内容,都强制重新生成库符号表
(5)将静态库拷贝到 linux 的库目录(/usr/lib或/lib)下
这里我自己创建了一个存我编译好的静态库
(6)使用test.c调用静态库
#include "mylib.h"
#include <stdio.h>
int main(){printf ("create and use library:\n");welcome(); //静态库中的函数原型outstring("It’s successful\n"); //静态库中的函数原型
}