1. GCC 介绍
1.1 介绍
GCC 官方文档 https://gcc.gnu.org/onlinedocs/
- 官方文档是最权威的,网上所有的答案都来自官方文档
- 国内论坛参差不齐,找到好的答案比较花时间,并且很容易被错误的文档误导。所以推荐看官方文档靠谱点,并且可以提升英语阅读能力
GCC
(GNU Compiler Collection) GNU编译程序集合, 其他所有开放源码软件都在某种程度上依赖于它,甚至其他语言,例如Python
都是由C语言开发,由GNU编译程序编译的。
1.2 GCC常见的组成部分
GCC是由很多组件组成的,常见的组成部分如下:
c++
: 它是gcc的一个版本,默认语言设置为C++
,而且在连接的时候自动包含标注C++库,这和g++一样。(c++与g++组件功能基本上一样的,我们更常用g++
)configure
: GCC源代码根目录中的一个脚本,用于设置配置值
和创建GCC编译程序必须的make程序文件gcc
: 主要用来编译C
语言程序,该驱动程序等同于执行编译程序和连接程序以产生需要的输出g++
: gcc的一个版本,默认语言设置为C++
,而且在链接的时候自动包含标准的C++库
,这和c++
一样libgcc
: 该库包含的例程作为编译程序的一部分,是因为他们可被链接到实际的可执行程序中,它们是特殊的例程,链接到可执行程序,来执行基本的任务,例如浮点运算,这些库中的例程通常都是平台相关的libstdc++
:运行时库(动态库
),包含定义为标准语言一部分的所有C++类和函数
1.3 GCC包含的常见软件
下面介绍的GCC
常见软件,每一个软件和程序的编译和运行是密切相关的
ar
: 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通过使用该工具是为了创建和管理连接程序使用的目标库文档。该程序是binutils包的一部分。as
: GNU的汇编器
,它可以被编译或能够在各种不同平台上工作。gdb
: GNU的调试器
,不管是用vscode
还是vs2015
,在debug c++程序时,底层都是调用GNU
调试器来执行调试任务。gprof
: 该程序会监督编译程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供的配置文件来优化程序。(在实际工作中,接触会比较少)ld
: GNU的连接程序,利用该程序将目标文件链接为可执行程序或者库文件(实际工作中用的非常多)libtool
: 一个基本库,支持make程序的描述文件使用的简化共享库用法的脚本。make
: 一个工具程序,它会读makefile脚本
来确定程序的哪个部分需要编译和链接
,然后发布必要的命令。它读的脚本(叫做makefile
或者Makefile
)定义了文件关系和依赖关系,执行make进行自动批量编译程序
,解放我们的双手。
1.4 GCC默认头文件搜索路径
- 头文件对于编译来说是非常重要的,在编译程序时候,
一定要把需要依赖的头文件的搜索路径都要Include
进来。 - 在github中下载一些项目上,经常会遇到一些非常头疼的问题。会经常报错显示
找不到xx头文件
, 在学会makefile
编写后,相信遇到这样的问题,可以轻松的去解决。 - 可以通过下面的命令来查看
GCC
默认的头文件搜索路径
echo | gcc -v -x c -E -
/usr/lib/gcc/x86_64-linux-gnu/7/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
2. 编译过程
2.1 hello world 在计算机中的表示
hello程序
的生命周期是从一个源程序(或者说源文件)开始的,即程序员通过编译器创建并保存的文本文件,文件名为hello.c
。
源程序其实是由值0和1组成的位(又称为比特)序列。8个位被组织成一组,称为字节,每个字节表示程序中的某些文件字符。
一般.c
后缀的是c语言脚本文件,.cpp
后缀的是c++
语言的脚本文件。但是在linux
系统中,其实是不分文件后缀的,对它来说一个文件就是一串比特,没有任何别的意义,加后缀
主要是使用者的习惯
,为了更加方便的辨别是哪种语言编写的文件,对计算机来说是没有区别的。
大部分计算机使用ASCII
标准来表示文本字符
- 用一个唯一的单字节大小的整数值信息来表示每个字符
- hello.c程序是以字节序列的方式存储在文件中的
hello.c 的表示方法说明了一个基本思想:系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的
。
2.2 编译过程 程
-
程序的生命周期是从一个高级的C/C++语言程序开始的。
-
为了在系统中运行程序,以c语言的
hello.c
来说,每条C语句必须被底层程序转化
为一系列低级的机器语言指令
(010101的格式)。 -
然后这些指令按照一种称为
可执行目标程序
的格式打包好
,并以二进制磁盘文件
的形式存放
起来,目标程序也称为可执行目标文件。 -
GCC编译器读取程序文件
hello.c
, 并把它翻译成一个可执行目标文件hello
, 这个编译过程
可分为四个阶段
完成,如下图所示,执行这四个阶段的程序(预处理器、编译器、汇编器和链接器
)一起构成了编译系统(compilation system)。
2.2.1 预处理阶段
预处理器
(cpp) 根据以字符#
开头的命令(头文件), 修改原始的C
程序。比如hello.c
中第一行的#include <stdio.h>
命令告诉预处理器读取系统头文件stdio.h
中的内容,并把它直接插入到程序文本中,结果就得到了另一个C
程序,通常是以.i
作为文件扩展名。
2.2.2 编译阶段
编译器(ccl)
将文本文件hello.i
翻译成文本文件hello.s, 它是一个汇编语言
程序,该汇编程序对函数main的定义,如下所示:
main:
subq $8, %rsp
mov1 $.LCO,%edi
call puts
mov1 $0,%eax
addq $8,%rsp
ret
每条语句都以一种文本格式描述了一条低级机器语言指令。汇编语言非常有用,它为不同高级语言的不同编译器提供了通用的输出语言
2.2.3 汇编阶段
汇编器(as)
: 将hello.s
编译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocation object program)的格式,并将结果保存在目标文件的hello.o
中,
hello.o
文件是一个二进制文件,它包含17个字节是函数main
的指令编码。如果我们在文本编辑器中打开hello.o
,将看到一堆乱码
2.2.4 链接阶段
注意,hello程序调用了printf函数,它是每个C编译器都提供的标准C库中的一个函数。printf
函数存在于一个名为printf.o
的单独的预编译好的目标文件中,而这个文件必须以某种方式合并到我们的hello.o程序中
连接器(ld)
就负责处理这种合并,并输出可执行文件hello文件,可以被加载到内存中,有系统执行。
3. 程序在计算机内的存储
系统会花费大量的时间和步骤把信息从一个地方挪到另一个地方:
- hello 程序的机器指令最初是放在磁盘上的
- 当程序加载时,他们被复制到主存
- 当处理器运行程序时,指令又从主存中复制到处理器中
相似地,数据串hello world\n
开始在磁盘上,然后被复制到主存,最后从主存上复制到显示设备。