gcc命令
介绍
GCC (GNU Compiler Collection)是 GNU 工具链的主要组成部分,是一套以GPL和LGPL许可证发布的程序语言编译器自由软件。
GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C 语言,但如今的 GCC 不仅可以编译 C、C++ 和 Objective-C,还可以通过不同的前端模块支持各种语言,包括 Java、Fortran、Ada、Pascal、Go 和 D 语言等等。
GCC 的编译过程可以划分为四个阶段:
- 预处理(Pre-Processing)
- 编译(Compiling)
- 汇编(Assembling)
- 链接(Linking)
Linux 程序员可以根据自己的需要控制 GCC 的编译阶段,以便检查或使用编译器在该阶段的输出信息,帮助调试和优化程序。以 C 语言为例,从源文件的编译到可执行文件的运行,整个过程大致如下。
各文件后缀说明如下:
后缀 | 描述 | 后缀 | 描述 |
---|---|---|---|
.c | C 源文件 | .s/.S | 汇编语言源文件 |
.C/.cc/.cxx/.cpp | C++ 源文件 | .o/.obj | 目标文件 |
.h | C/C++ 头文件 | .a/.lib | 静态库 |
.i/.ii | 经过预处理的 C/C++ 文件 | .so/.dll | 动态库 |
语法:
gcc [options] file...
在阶段编译示例之前,我们先来介绍下示例中用到的参数。
-o <file>
:指定输出文件。
-E
:仅执行预处理(不要编译、汇编或链接)。
-S
:只预处理和编译(不汇编或链接)。
-c
:预处理、编译和汇编,但不链接。
示例
阶段编译
假设有文件 hello.c
,内容如下:
#include <stdio.h>
int main(void)
{
printf("Hello World!");
return 0;
}
.c
输出a.out
编译 hello.c
,默认输出a.out
gcc hello.c
.c
源文件输出.i
预处理文件
只执行预处理,输出hello.i
文件
gcc -E hello.c -o hello.i
.c
源文件/.i
预处理文件输出.s
汇编文件
只执行预处理和编译,输出hello.s
汇编文件
gcc -S hello.c
也可以由hello.i
文件生成hello.s
汇编文件
gcc -S hello.i -o hello.s
.c
源文件/.i
预处理文件/.s
汇编文件输出.o
目标文件
只执行预处理、编译和汇编,输出hello.o
文件
gcc -c hello.c
也可以由hello.i
或hello.s
生成hello.o
目标文件
gcc -c hello.i -o hello.o
gcc -c hello.s -o hello.o
.o
目标文件链接成可执行文件
gcc hello.o -o hello
使用静态库
在使用静态库示例之前,我们先来介绍下示例中用到的参数。
-static
:此选项将禁止使用动态库,所以生成文件比较小,但是需要系统有动态库。
-L
:指定链接库的包含路径。
-l
:链接时搜索指定的函数库。
创建一个foo.c
源文件,内容如下:
#include <stdio.h>
void foo(void)
{
printf("Here is a static library\n");
}
将.c
源文件编译成静态库.a
gcc -c foo.c # 生成foo.o目标文件
ar rcs libfoo.a foo.o # 生成libfoo.a静态库。 ar Linux命令,用于建立或修改库,
# rcs是ar命令的参数。
修改hello.c
文件,调用foo
函数
#include <stdio.h>
void foo(void);
int main(void)
{
printf("Hello World!\n");
foo();
return 0;
}
编译.c
文件,并链接静态库.a
(加上 -static
选项)
gcc hello.c -static libfoo.a -o hello
也可以使用-L
指定库的搜索路径,并使用-l
指定库名
gcc hello.c -static -L. -lfoo -o hello
运行结果
使用共享库
修改 foo.c 文件,内容如下:
#include <stdio.h>
void foo(void)
{
printf("Here is a shared library\n");
}
将.c
源文件编译为 动态库/共享库。由于动态库可以被多个进程共享加载,所以需要使用-fPIC
选项生成位置无关的代码。
gcc foo.c -shared -fPIC -o libfoo.so
编译.c
并链接共享库.so
gcc hello.c libfoo.so -o hello
但是此时运行 hello 程序失败
原因是找不到.so
共享库
这是因为.so
并不在 Linux 系统的默认搜索目录中,解决办法是我们主动告诉系统,.so
共享库在哪里。
方法一:设置环境变量LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$(pwd) # Linux 命令
将.so
所在的当前目录添加到LD_LIBRARY_PATH
变量,再次执行hello
。
方法二:使用rpath
将共享库位置嵌入到程序
gcc hello.c -L. -lfoo -Wl,-rpath='pwd' -o hello
rpath
即run path
,是种可以将共享库位置嵌入程序中的方法,从而不用依赖于默认位置和环境变量。这里在链接时使用 -Wl,-rpath=/path/to/yours
选项,-Wl
会发送以逗号分隔的选项到链接器,注意逗号分隔符后面没有空格。
这种方式要求共享库必须有一个固定的安装路径,欠缺灵活性,不过如果设置了LD_LIBRARY_PATH
,程序加载时也是会到相应路径寻找共享库的。
方法三:将.so
共享库添加到系统路径
sudo cp libfoo.so /usr/lib/ # Linux 命令
如果 hello 程序仍然运行失败,请尝试执行 ldconfig
命令更新共享库的缓存列表。
执行程序
此时,再次查看 hello 程序的共享库依赖
可以看到.so
已经被发现了,其中/lib
是/usr/lib
目录的软链接。
a.out
文件介绍
a.out
是“assembler output
”(汇编程序输出)的缩写形式。
在 Linux 下编译链接程序时,如果不用-o
选项来指定输出文件名称,默认情况下就输出名为a.out
可执行文件。
为什么默认是a.out
而不是别的名称呢?这是一个历史遗留问题。
**注意:**在 Linux 图形界面无法双击运行 gcc生成的a.out
,一般是因为编写的程序是控制台程序,而不是 GUI 程序,不是程序不运行,而是因为没有 GUI 界面,所以看不到效果。正确的做法是在终端运行./a.out
。
参考: https://blog.youkuaiyun.com/To_Be_IT_1/article/details/30212467
.i
文件介绍
预处理源文件后,预处理器执行宏替换、条件编译以及包含指定的文件。
.s
文件介绍
用汇编语言编写的源代码文件。可以汇编为目标文件的纯汇编代码。
大写.S
文件代表仍必须通过预处理的汇编代码。
.o文件介绍
object
,目标文件,相当于 Windows 下的obj
文件,该文件是指源代码经过编译过程产生的且能被 CPU 直接识别的二进制代码。
可执行文件介绍
Windows 下后缀exe
(executable ),Linux 不使用文件名扩展来识别文件类型,所以一般没有后缀名,有可执行权限的文件都是可执行文件,与后缀没有关系。当程序要执行时还需要进行链接(link),链接之后再将一个或多个.o
文件与系统库文件链接,就成了一个可执行文件。
参考: https://getiot.tech/zh/linux-command/gcc