C 或者 C++ 程序从源代码生成可执行程序的过程,需经历 4 个过程,分别是预处理、编译、汇编和链接。

同样,使用 GCC 编译器编译 C 或者 C++ 程序,也必须要经历这 4 个过程。但考虑在实际使用中,用户可能并不关心程序的执行结果,只想快速得到最终的可执行程序,因此 gccg++ 都对此需求做了支持。

#include <iostream>int main()
{std::cout << "hello,world" << std::endl;return 0;
}

编写如下指令并执行


wohu@ubuntu:~/cpp/src$ g++ test.cpp
wohu@ubuntu:~/cpp/src$ ls
a.out  test.cpp

同样,GCC 编译器会在当前目录下生成一个名为 a.out 的可执行文件(如果之前有同名文件,旧文件会被覆盖)。通过如下指令即可运行该文件:

wohu@ubuntu:~/cpp/src$ ./a.out
hello,world
wohu@ubuntu:~/cpp/src$

注意,gcc 或者 g++ 指令还支持用户手动指定最终生成的可执行文件的文件名,

wohu@ubuntu:~/cpp/src$ g++ test.cpp  -o main
wohu@ubuntu:~/cpp/src$ ls
a.out  main  test.cpp
wohu@ubuntu:~/cpp/src$ ./main
hello,world
wohu@ubuntu:~/cpp/src$

其中 -o 选项用于指定要生成的文件名,例如 -o main 即表示将生成的可执行文件名设为 main

1. gcc -o选项

gcc -o 选项用来指定输出文件,如果不使用 -o 选项,那么将采用默认的输出文件。例如默认情况下,生成的可执行文件的名字默认为 a.out

如下是 gcc -o 指令的使用语法格式:

gcc [-E|-S|-c] [infile] [-o outfile]

其中,用方括号 [] 括起来的部分可以忽略。

  • [infile] 表示输入文件(也即要处理的文件),它可以是源文件、汇编文件或者目标文件;
  • [outfile] 表示输出文件(也即处理的结果),可以是预处理文件、目标文件、可执行文件等;

值得一提的是,通常情况下 [infile] 处放置一个文件,但根据实际需要也可以放置多个文件,表示有多个输入文件。

注意,虽然我们仅编写了一条 gcc 或者 g++ 指令,但其底层依据是按照预处理、编译、汇编、链接的过程将 CC++ 程序转变为可执行程序的。而本应在预处理阶段、编译阶段、汇编阶段生成的中间文件,此执行方式默认是不会生成的,只会生成最终的 a.out 可执行文件(除非为 gcc 或者 g++ 额外添加 -save-temps 选项)。

查看该过程中产生的中间文件。如此,上面介绍的执行方式将不再使用,而要采用分步编译的方式。

所谓“分步编译”,即由用户手动调用 GCC 编译器完成对 C、C++源代码的预处理、编译、汇编以及链接过程,每个阶段都会生成对源代码加工后的文件。

GCC 常用的编译选项

GCC 手册

接下来将对如何实现分步编译做详细的讲解,即如何将一个源代码程序经历预处理、编译、汇编以及链接这 4 个过程,最终生成对应的可执行程序。

2. 预处理

无论是 C 还是 C++ 程序,其从源代码转变为可执行代码的过程,具体可分为 4 个过程,分别为预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。

默认情况下,gcc 指令会一气呵成,直接将源代码历经这 4 个过程转变为可执行代码,且不会保留各个阶段产生的中间文件。

而如果想查看这 4 个阶段各自产生的中间文件,最简单直接的方式就是对源代码进行“分步编译”,即控制 GCC 编译器逐步对源代码进行预处理、编译、汇编以及链接操作。其中,通过为 gcc 指令添加 -E 选项,即可控制 GCC 编译器仅对源代码做预处理操作。

所谓预处理操作,主要是处理那些源文件和头文件中以 # 开头的命令(比如 #include、#define、#ifdef 等),并删除程序中所有的注释 // 和 /* … */

默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。因此该指令往往会和 -o 选项连用,将结果导入到指令的文件中。比如:

wohu@ubuntu:~/cpp/src$ g++ -E test.cpp -o test.ii
wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.ii
wohu@ubuntu:~/cpp/src$

Linux 系统中,通常用 .i 或者 .ii 作为 C/C++ 程序预处理后所得文件的后缀名。由此,就完成了 test.cpp 文件的预处理操作,并将其结果导入到了 test.ii 文件中。

读者可以通过执行 cat test.ii 指令查看该文件中的内容,但通常没有足够 C++ 语言功底的读者是看不懂的。为此,我们可以为 g++ 指令再添加一个 -C 选项,阻止 GCC 删除源文件和头文件中的注释:

wohu@ubuntu:~/cpp/src$ g++ -E -C test.cpp -o test.ii

注意,这里是大写的 -C,不是小写的 -c。小写的 -c 另作他用,

gcc -E 支持的常用选项

其中,对于指定 #include 搜索路径的几个选项,作用的先后顺序如下:

  • 对于用 #include "" 引号形式引入的头文件,首先搜索当前程序文件所在的目录,其次再前往 -iquote 选项指定的目录中查找;
  • 前往 -I 选项指定的目录中搜索;
  • 前往 -isystem 选项指定的目录中搜索;
  • 前往默认的系统路径下搜索;
  • 前往 -idirafter 选项指定的目录中搜索;

3. 编译

所谓编译,简单理解就是将预处理得到的程序代码,经过一系列的词法分析、语法分析、语义分析以及优化,加工为当前机器支持的汇编代码。

通过给 g++ 指令添加 -S(注意是大写)选项,即可令 GCC 编译器仅将指定文件加工至编译阶段,并生成对应的汇编代码文件。例如:

wohu@ubuntu:~/cpp/src$ g++ -S test.ii
wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.ii  test.s
wohu@ubuntu:~/cpp/src$

可以看到,经过执行 g++ -S 指令,其生成了一个名为 test.s 的文件,这就是经过编译的汇编代码文件。也就是说默认情况下,编译操作会自行新建一个文件名和指定文件相同、后缀名为 .s 的文件,并将编译的结果保存在该文件中。

我们还可以为 gcc -S 指令添加 -o 选项,令 GCC 编译器将编译结果保存在我们指定的文件中。例如:

wohu@ubuntu:~/cpp/src$ g++ -S test.ii -o demo.s
wohu@ubuntu:~/cpp/src$ ls
demo.s  test.cpp  test.ii  test.s
wohu@ubuntu:~/cpp/src$

需要注意的是,gcc -S 指令操作的文件并非必须是经过预处理后得到的 .i.ii 文件,-S 选项的功能是令 GCC 编译器将指定文件处理至编译阶段结束。这也就意味着,gcc -S 指令可以操作预处理后的 .i.ii 文件,也可以操作源代码文件:

  • 如果操作对象为 .i.ii 文件,则 GCC 编译器只需编译此文件;
  • 如果操作对象为 .c 或者 .cpp 源代码文件,则 GCC 编译器会对其进行预处理和编译这 2 步操作。

因此,如果我们想直接得到 test.cpp 文件对应的汇编文件,就可以借助 gcc -S 指令:

wohu@ubuntu:~/cpp/src$ g++ -S test.cpp -o test.s
wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.s
wohu@ubuntu:~/cpp/src$

由此,我们就可以直接获得 test.cpp 对应的 test.s 汇编文件。

如果想提高文件内汇编代码的可读性,可以借助 -fverbose-asm 选项,GCC 编译器会自行为汇编代码添加必要的注释,例如:

wohu@ubuntu:~/cpp/src$ g++ -S test.cpp -o test.s -fverbose-asm
wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.s
wohu@ubuntu:~/cpp/src$

4. 汇编

如何对已得到的 test.s 执行汇编操作,并得到相应的目标文件,需要进行汇编操作。所谓目标文件,其本质为二进制文件,但由于尚未经过链接操作,所以无法直接运行。

简单地理解,汇编其实就是将汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令。相对于编译操作,汇编过程会简单很多,它并没有复杂的语法,也没有语义,也不需要做指令优化,只需要根据汇编语句和机器指令的对照表一一翻译即可。

通过为 gcc 指令添加 -c 选项(注意是小写字母 c),即可让 GCC 编译器将指定文件加工至汇编阶段,并生成相应的目标文件。例如:

wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.s
wohu@ubuntu:~/cpp/src$ g++ -c test.s
wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.o  test.s
wohu@ubuntu:~/cpp/src$

可以看到,该指令生成了和 test.s 同名但后缀名为 .o 的文件,这就是经过汇编操作得到的目标文件。

还可以为 gcc -c 指令在添加一个 -o 选项,用于将汇编操作的结果输入到指定文件中,例如:

wohu@ubuntu:~/cpp/src$ g++ -c test.s  -o test_obj.o
wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.o  test_obj.o  test.s
wohu@ubuntu:~/cpp/src$

需要强调的一点是,和 g++ -S 类似,g++ -c 选项并非只能用于加工 .s 文件。事实上,-c 选项只是令 GCC 编译器将指定文件加工至汇编阶段,但不执行链接操作。这也就意味着:

  • 如果指定文件为源程序文件(例如 test.cpp),则 gcc -c 指令会对 test.cpp 文件执行预处理、编译以及汇编这 3 步操作;
  • 如果指定文件为刚刚经过预处理后的文件(例如 test.i),则 gcc -c 指令对 test.i 文件执行编译和汇编这 2 步操作;
  • 如果指定文件为刚刚经过编译后的文件(例如 test.s),则 gcc -c 指令只对 test.s 文件执行汇编这 1 步操作。

注意,如果指定文件已经经过汇编,或者 GCC 编译器无法识别,则 gcc -c 指令不做任何操作。

5. 链接

总的来说链接阶段要完成的工作,就是将同一项目中各源文件生成的目标文件以及程序中用到的库文件整合为一个可执行文件。

目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,因此还无法执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。

完成链接操作,并不需要给 g++ 添加任何选项,只要将汇编阶段得到的 test.o 作为参数传递给它,g++就会在其基础上完成链接操作。例如:

wohu@ubuntu:~/cpp/src$ ls
test.cpp  test.o  test_obj.o  test.s
wohu@ubuntu:~/cpp/src$ g++ test.o
wohu@ubuntu:~/cpp/src$ ls
a.out  test.cpp  test.o  test_obj.o  test.s
wohu@ubuntu:~/cpp/src$ g++ test_obj.o -o test_obj
wohu@ubuntu:~/cpp/src$ ls
a.out  test.cpp  test.o  test_obj  test_obj.o  test.s
wohu@ubuntu:~/cpp/src$ ./a.out
hello,world
wohu@ubuntu:~/cpp/src$ ./test_obj
hello,world
wohu@ubuntu:~/cpp/src$

gcc 会根据所给文件的后缀名 .o,自行判断出此类文件为目标文件,仅需要进行链接操作。
通过分别执行这 a.outtest_obj 可执行文件,其执行结果完全相同,说明 test.otest_obj.o 是完全相同的。

6. 一步生成所有中间结果

如果读者不想执行这么多条指令,但想获得预处理、编译、汇编以及链接这 4 个过程产生的中间文件,可以执行如下指令:

wohu@ubuntu:~/cpp/src$ ls
test.cpp
wohu@ubuntu:~/cpp/src$ g++ test.cpp -save-temps
wohu@ubuntu:~/cpp/src$ ls
a.out  test.cpp  test.ii  test.o  test.s
wohu@ubuntu:~/cpp/src$

可以看到,通过给 g++ 添加 -save-temps 选项,可以使 GCC 编译器保留编译源文件过程中产生的所有中间文件。

GCC 编译 C++ 程序分步骤流程(预处理 gcc -E、编译 gcc -S、汇编 gcc -c 和链接 gcc 以及 gcc -o 选项)相关推荐

  1. 微信小程序模拟ppt效果的公司介绍分步骤流程(主要使用Animation)。使用时间轴介绍以及swiper

    思路: 参考抖音上滑下滑翻页实现介绍. 封装watch方法监听翻页并分步骤进入动画. 使用左滑右滑.渐变.计数.文本框打字输入动画. Step1: 上滑下滑 //wxml <view class ...

  2. c语言conflicting types,gcc编译C程序出现”error conflicting types for function”编译错误的分析解决...

    今天使用gcc编译C语言程序时出现 "error conflicting types for function" 编译错误,这个错误的原因是什么?如何解决?以下看正文的讲解. 在使 ...

  3. c gui qt 4编程第二版_面试官问Linux下如何编译C程序,如何回答?为你编译演示

    文章来源:嵌入式大杂烩 作者:ZhengNL Windows下常用IDE来编译,Linux下直接使用gcc来编译,编译过程是Linux嵌入式编程的基础,也是嵌入式高频基础面试问题. 一.命令行编译及各 ...

  4. 编译Java程序一共要安装什么_如何编译java程序

    如何编译java程序 java编程要如何编译java程序?编译java程序要注意什么?代码是什么?下面跟yjbys一起来学习一下. 要安装好jdk,设置好相应的环境变量,然后在命令行里面javac. ...

  5. 微信小程序分账流程及功能

    添加分账接收方 单次请求 下载资源添加链接描述

  6. linux如何用gcc编译c程序,Ubuntu之如何使用gcc编译C语言程序

    在大学里面大家都用过VC6.0来进行编程.VC6.0属于集成开发环境,一份代码从文本变成可执行的程序只需要在其中点击几个按钮就行了,加之老师也只教过我们如何操作,并未介绍C语言的执行过程.上一篇曾提到 ...

  7. 深入理解使用CMake编译 NDK 程序

    使用 CMake 进行Android NDK编译的原理 介绍 Android Studio 2.2 及以后的版本默认使用CMake进行 NDK 编译, 其中最吸引人的地方是,在开发NDK程序时可以进行 ...

  8. gcc编译c语言多线程程序的步骤,GCC分步骤编译C语言程序

    上节<GCC编译C语言程序完整演示>讲解的是通过gcc命令一次性完成编译和链接的整个过程,这样最方便,大家在学习C语言的过程中一般都这么做.实际上,gcc命令也可以将编译和链接分开,每次只 ...

  9. Linux下C语言程序编写及执行和分步骤编译链接C源代码

    实验一:Linux下C语言程序编写及执行 //hello.c #include <stdio.h> int main(){int x = 1;int y = 2;int sum = x + ...

最新文章

  1. posix_kill 信号
  2. CV之API:利用Face++的人体识别接口,实现摄像头实时手势识别
  3. Python风格总结:Print输出
  4. 计算机应用基础问题,计算机应用基础常见问题
  5. Spark采用分区方式读取数据库时partitionColumn, lowerBound, upperBound, numPartitions的理解与验证
  6. html中心点缩放,html canvas 让物体随物体中心缩放
  7. java.lang.stringind_为什么越界了? java.lang.StringIndexOutOfBoundsException
  8. 不要只关注马斯克猎鹰,中国SpaceX在哪?商业航天我们不能缺席!
  9. 43种名车标志及来历
  10. Typora配置PicGo提示Failed to fetch问题解决
  11. 设计模式Java语言实现之策略模式
  12. Google翻译API的使用
  13. mysql中information_schema.columns字段说明
  14. 考研失败,应届毕业生该何去何从?大学生如何规划自己的找工作路?
  15. capslock键英语怎么读_Caps Lock怎么读
  16. Tivoli 安装参考
  17. MYSQL修改报错 You can‘t specify target table ‘表名‘ for update in FROM clause
  18. AcWing 1761. 阻挡广告牌
  19. 什么叫冷备用状态_在电力设备运行中有:运行、热备用、冷备用、检修四中状态,请问什么是热备用、冷备用?...
  20. LinkedIn TAG

热门文章

  1. C++核心编程(四)--文件操作
  2. log4j屏蔽掉某个包下的log日志打印
  3. 外网远程桌面连接设置
  4. pytorch旧版安装
  5. 最全Pycharm教程(43)——Pycharm扩展功能之UML类图使用 代码结构
  6. Docker暴露端口服务器公网IP无法访问问题排查
  7. 边缘网联与5G等服务器
  8. LLVM一些语法规则
  9. TVM/Relay 的 PartitionGraph()(mod) 函数讨论整理
  10. 智能驾驶开发的几个问题