C语言gcc编译过程以及常用编译选项

本文详细介绍了C代码从源文件到可执行程序的全过程,包括预处理、编译、汇编和链接四个阶段,并解释了GCC编译器常用选项的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    上篇文章知道了C代码编译后存放在内存中的位置,那么C代码的整个编译过程又是怎样的呢?一条命令gcc hello.c就可以编译成可执行程序a.out,然后./a.out之后就可以执行hello.c这个程序的代码了。下面的文章分析的不错,就整理了下。

hello.c:

#include<stdio.h>
int main()
{
        printf(“Hello World\n”);
        return 0;
}

实际上gcc hello.c可以分解为4个步骤,分别是预处理(Preprocess),编译(Compilation),汇编(Assembly)和链接(Linking)。


一、预处理

预处理过程主要读取c源程序,对伪指令和特殊符号进行处理。包括宏,条件编译,包含的头文件,以及一些特殊符号。基本上是一个replace的过程。

gcc –E hello.c –o hello.i 

以下为预处理后的输出文件hello.i的内容:

# 1"hello.c"
# 1"<built-in>"
# 1"<command-line>"
# 1"hello.c"
# 1 "/usr/include/stdio.h"1 3 4
# 28"/usr/include/stdio.h" 3 4
/***** 省略了部分内容,包括stdio.h中的一些声明及定义  *****/
# 2"hello.c" 2
int main()
{
 printf("Hello World\n");
 return 0;
}

预处理过程主要处理规则如下:

1、将所有的#define删除,并且展开所有的宏定义;

2、处理所有条件编译指令,如#if,#ifdef等;

3、处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。

4、删除所有的注释//和 /**/;

5、添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;

6、保留所有的#pragma编译器指令,因为编译器须要使用它们。


二、编译

编译过程通过词法和语法分析,确认所有指令符合语法规则(否则报编译错),之后翻译成对应的中间码,在linux中被称为RTL(Register Transfer Language),通常是平台无关的,这个过程也被称为编译前端。编译后端对RTL树进行裁减,优化,得到在目标机上可执行的汇编代码。gcc采用as作为其汇编器,所以汇编码是AT&T格式的,而不是Intel格式,所以在用gcc编译嵌入式汇编时,也要采用AT&T格式。


gcc –S hello.i –o hello.s 
以下为编译后的输出文件hello.s的内容

    .file  "hello.c"
        .section    .rodata
.LC0:
        .string      "HelloWorld"
        .text
.globl main
        .type         main, @function
main:
        pushl         %ebp
        movl          %esp, %ebp
        andl $-16, %esp
        subl  $16, %esp
        movl          $.LC0, (%esp)
        call   puts
        movl          $0, %eax
        leave
        ret
        .size main, .-main
        .ident        "GCC: (GNU)4.4.0 20090506 (Red Hat 4.4.0-4)"
        .section   .note.GNU-stack,"",@progbits

三、汇编

汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。

gcc –c hello.c –o hello.o


由于hello.o的内容为机器码,不能以文本形式方便的呈现。

 

四、链接

链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。

ld –static crt1.o crti.o crtbeginT.ohello.o –start-group –lgcc –lgcc_eh –lc-end-group crtend.o crtn.o  

当然链接的时候还会用到静态链接库,和动态连接库。静态库和动态库都是.o目标文件的集合。

静态库是在链接过程中将相关代码提取出来加入可执行文件的库(即在链接的时候将函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中),ar只是将一些别的文件集合到一个文件中。可以打包,当然也可以解包。

ar -v -q  test.a test.o  

上面指令可以生成静态链接库test.a

 

动态库在链接时只创建一些符号表,而在运行的时候才将有关库的代码装入内存,映射到运行时相应进程的虚地址空间。如果出错,如找不到对应的.so文件,会在执行的时候报动态连接错(可用LD_LIBRARY_PATH指定路径)。用file test.so可以看到test.so是shared object的ELF文件。

gcc -sharedtest.so test.o  

上面指令可以生成动态连接库test.so

 

好了,整个编译过程就如上所示了,那么对于gcc还有一些编译的选项的。具体如下:

 

GCC编译选项

1. -c    :编译产生对象文件(*.obj)而不链接成可执行文件,当编译几个独立的模块,而待以后由链接程序把它们链接在一起时,就可以使用这个选项,如:


gcc -c hello.c ===> hello.o  
gcc hello.o  

2. -o    :允许用户指定输出文件名,如

gcc hello.c -o hello.o  
or  
gcc hello.c -o hello 

3. -g   :指明编译程序在编译的输出中应产生调试信息.这个调试信息使源代码和变量名引用在调试程序中或者当程序异常退出后在分析core文件时可被使用.

 

4. -D  

      允许从编译程序命令行定义宏符号

    一共有两种情况:一种是用-DMACRO,相当于在程序中使用#define MACRO,另一种是用-DMACRO=A,相当于程序中的#define MACRO A.如对下面这代码:

#ifdef DEBUG  
     printf("debugmessage\n");  
#endif  

编译时可加上-DDEBUG参数,执行程序则打印出编译信息

 

5. -I  

         可指定查找include文件的其他位置.例如,如果有些include文件位于比较特殊的地方,比如/usr/local/include,就可以增加此选项如下:

gcc -c -I/usr/local/include -I/opt/include hello.c 

  此时目录搜索会按给出的次序进行.

 

6. -E  

         这个选项是相对标准的,它允许修改命令行以使编译程序把预先处理的C文件发到标准输出,而不实际编译代码.在查看C预处理伪指令和C宏时,这是很有用的.可能的编译输出可重新定向到一个文件,然后用编辑程序来分析:

gcc -c -E hello.c >cpp.out 

  此命令使include文件和程序被预先处理并重定向到文件cpp.out.以后可以用编辑程或者分页命令分析这个文件,并确定最终的C语言代码看起来如何.

 

7. -O  

       优化选项,这个选项不是标准的

           -O和 -O1指定1级优化

           -O2 指定2级优化

           -O3 指定3级优化

           -O0指定不优化

           gcc -c O3 -O0 hello.c 

       当出现多个优化时,以最后一个为准!!

 

8. -Wall 

       以最高级别使用GNU编译程序,专门用于显示警告用!!

gcc -Wall hello.c 

9. -L

       指定连接库的搜索目录,-l(小写L)指定连接库的名字

gcc main.o -L/usr/lib -lqt -o hello 

10.-share   

       此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库

 

11.-static  

        此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库

 

12.-fPIC

        表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。










### GCC 编译命令使用指南 GCC(GNU Compiler Collection)是一款功能强大的开源编译器套件,广泛用于 C 和 C++ 程序的编译。在 C 语言开发中,GCC 提供了丰富的命令行选项来控制编译流程的不同阶段,包括预处理、编译、汇编和链接。 #### 基本编译流程 C 程序的编译通常分为四个阶段:预处理、编译、汇编和链接。每个阶段都可以通过特定的 GCC 命令单独执行: - **预处理**:将源文件中的宏定义展开,并包含头文件内容。 ```bash gcc -E main.c -o main.i ``` - **编译**:将预处理后的 `.i` 文件转换为汇编代码。 ```bash gcc -S main.i -o main.s ``` 或者直接从 `.c` 文件开始: ```bash gcc -S main.c -o main.s ``` - **汇编**:将 `.s` 汇编文件转换为目标文件(`.o`)。 ```bash gcc -c main.s -o main.o ``` 同样地,也可以直接从 `.c` 文件生成目标文件: ```bash gcc -c main.c -o main.o ``` - **链接**:将一个或多个目标文件与库文件链接,生成最终的可执行文件。 ```bash gcc main.o -o main ``` 如果不需要分步操作,可以直接使用一条命令完成整个编译过程: ```bash gcc main.c -o main ``` #### 常见文件后缀说明 GCC 支持多种类型的输入文件,每种类型都有其特定的后缀名: | 后缀 | 含义 | |----------|------------------------| | `.c` | C 语言源文件 | | `.s`/`.S`| 汇编语言源文件 | | `.C`/`.cc`/`.cxx`/`.cpp` | C++ 源文件 | | `.o`/`.obj` | 目标文件 | | `.h` | C/C++ 头文件 | | `.i`/`.ii` | 经过预处理的 C/C++ 文件 | | `.a`/`.lib` | 静态库 | | `.so`/`.dll` | 动态库 | #### 多文件编译示例 对于包含多个源文件的项目,可以将所有 `.c` 文件一次性编译并链接成一个可执行文件。例如,假设有一个项目包含 `main.c` 和 `utils.c`: ```bash gcc main.c utils.c -o myprogram ``` 此命令会自动处理两个源文件的预处理、编译、汇编以及最终的链接操作。 #### 使用 Vim 编写并编译 C 程序 在 Linux 系统中,开发者常使用文本编辑器如 Vim 来编写 C 程序。以下是一个简单的示例: ```c #include <stdio.h> int main() { printf("hello world\n"); return 0; } ``` 保存为 `Test.c` 后,可以通过以下命令进行编译: ```bash gcc Test.c ``` 或者使用 `cc` 命令(它是 GCC 的别名): ```bash cc Test.c ``` 默认情况下,GCC 会生成名为 `a.out` 的可执行文件。运行该程序只需执行: ```bash ./a.out ``` 输出结果将是: ``` hello world ``` #### 编译选项与优化 除了基本的编译命令外,GCC 还提供了许多有用的选项来增强程序的功能或提高性能。一些常用选项包括: - `-Wall`:启用所有警告信息。 - `-Wextra`:启用额外的警告信息。 - `-g`:添加调试信息,便于使用 GDB 调试程序。 - `-O0` 到 `-O3`:设置不同的优化级别,其中 `-O0` 表示不优化,而 `-O3` 表示最高级别的优化。 例如,开启所有警告并加入调试信息: ```bash gcc -Wall -Wextra -g main.c -o main ``` #### 静态库与动态库的构建 静态库和动态库是组织大型项目的常用方式。创建静态库的过程如下: 1. 编译目标文件: ```bash gcc -c utils.c -o utils.o ``` 2. 创建静态库: ```bash ar rcs libutils.a utils.o ``` 3. 使用静态库编译主程序: ```bash gcc main.c libutils.a -o main ``` 对于动态库(共享库),步骤类似: 1. 编译位置无关代码: ```bash gcc -fPIC -c utils.c -o utils.o ``` 2. 创建动态库: ```bash gcc -shared -o libutils.so utils.o ``` 3. 使用动态库编译主程序: ```bash gcc main.c -L. -lutils -o main ``` 注意,在运行时需要确保系统能够找到动态库路径,可以通过设置环境变量 `LD_LIBRARY_PATH` 实现: ```bash export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值