Linux 系统 C / C++ 代码编译器 —— gcc / g++
- 编译器使用
- 可能存在的版本问题
- 解决
- 简单使用
- 指定生成的可执行程序文件名
- 匹配使用
- 程序的翻译过程
- 预处理
- 实操
- 条件编译
- 编译
- 实操
- 汇编
- 实操
- 链接
- 实操
- 为什么会有这些过程
编译器使用
可能存在的版本问题
gcc / g++
是开发必备的编译器,由于前面学习过 yum
这里对于安装就不说了(网上一大把),但如果图省事,是 yum
默认安装的 gcc / g++
,版本会比较低,如下:
[root@localhost compiler]$ g++ --version
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.[root@localhost compiler]$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
解决
可以 添加选项 来 支持高版本
- 对于
gcc
来说,可添加-std=c99
选项支持高级特性(可能会有问题,后面说) - 对于
g++
来说,可添加-std=c++11
选项支持高级特性
初阶使用 并不建议 直接安装高版本编译器,因为 可以通过是否添加选项来细致理解语言不同版本的差别和特性
简单使用
gcc
和 g++
的用法几乎一样,简单的使用就是直接跟上需要编译的文件名就行:
gcc code.c
g++ code.cc
如果没有编译和链接报错,就会生成一个可执行的文件;
但有问题,新生成的可执行程序文件名是 a.out
(可以通过 ll
指令查看),在当前目录下,执行如下指令即可运行:
./a.out
指定生成的可执行程序文件名
如果我要指定生成的可执行程序文件名呢?如下添加 -o
选项即可( gcc
也是这样使用):
g++ code.cc -o my.exe
-o my.exe
选项跟在 g++
后面也行,这里的 my.exe
就是被指定的文件名,执行它即可运行:
./my.exe
匹配使用
- 编译器匹配:
gcc
只能编译 C 语言代码g++
既可以编译 C 语言代码,也可以编译 C++ 代码
- 文件名匹配
- C 语言代码只能用
.c
后缀 - C++ 可使用
.cc
、.cpp
和.cxx
后缀
- C 语言代码只能用
文件名要匹配不是给系统识别的( Linux 系统不以后缀标识文件类型),而是编译器 gcc / g++
要匹配,不然会报如下错(以文件 code.xxx
为例):
code.xxx: file not recognized: File format not recognized
collect2: error: ld returned 1 exit status
程序的翻译过程
首先要知道程序翻译过程中的所有步骤,分别是:
- 预处理
- 编译
- 汇编
- 链接
预处理
这是对一个代码文件的预处理,包括 宏替换、去注释、头文件展开、条件编译 等等,完成这些任务后,生成的输出是一个没有宏定义、条件指令或者包含文件的 “干净” 的 C 语言源文件
我们可以通过写一篇拥有 宏 、 注释 、 头文件 的 C 语言程序在 Linux
上面跑一下,利用 gcc
的选项来使翻译过程停在我们想要的步骤上
实操
那么假如现在有一篇 code.c
源文件,内容如下:
#include <stdio.h>
#define M 100int main()
{printf("C\n");for (int i = 0; i < 10; ++i){printf("Hello Linux!!! M: %d\n", M);}printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");//printf("Chehe\n");printf("Chehe\n");return 0;
}
可以通过如下指令进行 预处理 ,且停在 编译 之前:
gcc -E code.c -o code.i
-o
选项是对 预处理 后的文件进行命名(这里是 code.i
),不然会直接打印在命令行上,不好查看
-E
是指:从当前文件状态开始翻译,只进行 预处理,完成就停下
所以上述指令就是进行上述过程,将结果放进 code.i
中
这里就不展示结果长什么样,但会有 800 多行,里面大部分都是头文件的展开内容,实际上就是直接拷贝的 <stdio.h>
里的内容,只是会去掉 注释 ,相应 条件编译 之类的内容;
剩下的就会发现,上面所说的功能 预处理 都已完成
条件编译
大多数应该都知道或者了解,但 条件编译 未必清晰明了
有时候在下载相关软件的时候会发现有免费版,社区版,专业版之类的,常识来说这些对应的版本就是存在功能阉割, 专业版的功能更加多元强大 ,那这里有个问题:不同版本之间是不同的代码吗?
并不是,因为就算是功能阉割,但绝大多数功能都是具备的,如果要维护好几份不同的代码,岂不是麻烦(如果在共同功能上有 Bug ,那是否要修改好几次,测试好几次呢)?我们可以利用 条件编译
首先要记住 gcc
可以 对代码进行动态裁剪 ,绝对是可以的!!!
我们来验证一下:
写上这样一份代码并编译:
// proj.c 文件
#include <stdio.h>
void Diff_Vers()
{
#ifdef V1printf("功能1\n");printf("功能2\n");
#elif V2printf("功能1\n");printf("功能2\n");printf("功能3\n");printf("功能4\n");
#elseprintf("功能1\n");printf("功能2\n");printf("功能3\n");printf("功能4\n");printf("功能5\n");printf("功能6\n");
#endif
}
int main()
{Diff_Vers();return 0;
}
gcc proj.c -o proj.exe
此时运行 proj.c
文件,显示出来的就是 6 个功能都有,可利用指令 gcc -E proj.c -o proj.i
来查看编译器对条件编译的动态裁剪情况
上面没有定义 V1
或 V2
,就动态裁剪了 V1
和 V2
对应的代码
若 命令行宏定义 V1
,则 只保留 V1
:
gcc -DV1=1 proj.c -o proj.exe
若 命令行宏定义 V2
,则 只保留 V2
:
gcc -DV2=1 proj.c -o proj.exe
这里的 -D
选项就是为我们定义了 V1
或 V2
的宏定义,实现代码的动态裁剪
编译
记住其 核心作用 就是帮我们 做语法检查;
我们平时的程序报错无非就三种:
- 编译时语法报错 ,这就是很简单也很明了的错误,遵循相关语法即可,编译器甚至会告诉你报错在第几行
- 链接式报错
- 运行时报错
编译 会在 预处理 后的源文件的基础上,进行词法语法分析,主要工作就是 把 C 语言变成汇编语言
实操
可以通过如下指令进行 编译 ,且停在 汇编 之前:
gcc -S code.i -o code.s -std=c99
-S
是指:从当前文件状态开始翻译, 编译 完成就停下
这时可以查看 code.s
汇编代码文件
汇编
汇编就很恶心了,目前来说比较陌生,但其核心工作就是 把汇编语言翻译成为二进制目标文件
实操
可以通过如下指令进行 汇编 ,且停在 链接 之前:
gcc -c code.s -o code.o
-S
是指:从当前文件状态开始翻译, 汇编 完成就停下
这时可以查看 code.o
二进制目标文件 ,此时文件内容已经乱码,面目全非
链接
虽说二进制文件已经和机器语言很接近了,但还是不能直接交给底层去运行,因为少 链接 ,什么意思呢?
平时写代码时,我们会自己把所有的实现细节都自己写出来吗?不会,而是复用人家的第三方库对吧(提高开发效率),那库里的东西你凭什么可以直接拿过来用呢?又不是你写的,所以就需要 将人家的 C 标准库(以头文件加库的形式提供给你)文件(或者是更多其他文件)和自己的二进制目标文件进行链接,形成可执行程序
而链接的库分为 静态库 和 动态库 ,下面是 后缀名:
所在系统 | 静态库 | 动态库 |
---|---|---|
Linux | .a | .so |
Windows | .lib | .dll |
当然分别对应的链接方式就是 静态链接 和 动态链接
而 动态库 也是 共享动态库 ,当你链接这个库时,会将库中相关代码加载进内存,当未来有其他程序也要使用这个库时,可以直接跳过来执行同一份库代码,所以资源占用不多,但一旦 动态库 缺失,后果将因为找不到库而 链接 报错(第一次运行),程序崩溃(后来因某种原因缺失 动态库 )
但 静态库 对应的 静态链接,是在编译的时候,把库的方法拷贝进自己的可执行程序中,未来执行时,就算没有这个静态库,也丝毫不影响程序运行,不用关心库问题,可移植性高,跨平台能力强;但如果其他程序都需要此库,将会大量加载相同代码,导致资源占用多,速度降低,性能有问题
你可以通过指令 ldd
来查看一个可执行程序依赖的库:
ldd 可执行程序名
ldd /usr/bin/ls
ldd /usr/bin/pwd
实操
可以通过如下指令进行 链接 :
gcc code.o -o code.exe
此时已经可以运行 code.exe
文件了
为什么会有这些过程
因为历史选择
在还没有 C 语言但有汇编语言的时候,汇编代码需要编译器吗?当然需要!
那 C 语言编译器还需要重新实现一遍如何把汇编语言变成二进制文件吗?自然就不需要了,因为已经有汇编代码的编译器,所以 C 语言编译器真正需要做的就是如何把 C 语言变成汇编代码,即 编译 这一步,而后面已经有前人的努力,所以就有这样一步一步的翻译过程,效率也最高