操作系统大致分为 2 大阵营,分别是 Windows 阵营和类 Unix 阵营(包括 Unix、Linux、Mac OS、安卓等)。通常情况下,Windows 系统下用户更习惯使用现有的 IDE 来编译程序;而类 Unix 系统下,用户更喜欢直接编写相应的 gcc 命令来编译程序。GNU 计划的实施可谓一波三折,最重要的一点是,虽然该计划为 GNU 操作系统量身定做了名为 Thr Hurd 的系统内核,但由于其性能比不上同时期诞生的 Linux 内核,最终 GNU 计划放弃 The Hurd 而选用 Linux 作为 GNU 操作系统的内核。在 Linux 内核的基础上,GNU 计划开发了很多系统部件,GCC 就是其中之一。
sudo apt install gcc
gcc --version
GCC 编译器已经为我们提供了调用它的接口,对于 C 语言或者 C++ 程序,可以通过执行 gcc 或者 g++ 指令来调用 GCC 编译器。
只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译
。可以这样理解,gcc 是 GCC 编译器的通用编译指令,因为根据程序文件的后缀名,gcc 指令可以自行判断
出当前程序所用编程语言的类别,比如:
- xxx.c:默认以编译 C 语言程序的方式编译此文件;
- xxx.cpp:默认以编译 C++ 程序的方式编译此文件。
- xxx.m:默认以编译 Objective-C 程序的方式编译此文件;
- xxx.go:默认以编译 Go 语言程序的方式编译此文件;
如果
使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件
。也就是说,对于 .c 文件来说,gcc 指令以 C 语言代码对待,而 g++ 指令会以 C++ 代码对待。但对于 .cpp 文件来说,gcc 和 g++ 都会以 C++ 代码的方式编译。如果想使用 gcc 指令来编译执行 C++ 程序,需要在使用 gcc 指令时,手动为其添加 -lstdc++ -shared-libgcc 选项,表示 gcc 在编译 C++ 程序时可以链接必要的 C++ 标准库。
g++
指令就等同于gcc -xc++ -lstdc++ -shared-libgcc
指令
GCC 版本 | C 语言常用标准 | |||||||
---|---|---|---|---|---|---|---|---|
C89/C90 | C99 | C11 | C17 | GNU90 | GNU99 | GNU11 | GNU17 | |
10.1 ~ 8.4 | c89 / c90 | c99 | c11 | c17/c18 | gnu90/gnu89 | gnu99 | gnu11 | gnu17/gnu18 |
7.5 ~ 5.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.9.4 ~ 4.8.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.7.4 | c89/c90 | c99(部分支持) | c11(部分支持) | gnu90/gnu89 | gnu99(部分支持) | gnu11(部分支持) | ||
4.6.4 | c89/c90 | c99(部分支持) | c1x(部分支持) | gnu90/gnu89 | gnu99(部分支持) | gnu1x(部分支持) | ||
4.5.4 | c89/c90 | c99(部分支持) | gnu90/gnu89 | gnu99(部分支持) |
GCC 版本 | C++ 常用标准 | |||||||
---|---|---|---|---|---|---|---|---|
C++98/03 | C++11 | C++14 | C++17 | GNU++98 | GNU++11 | GNU++14 | GNU++17 | |
10.1 ~ 8.4 | c++98/c++03 | c++11 | c++14 | c++17 | gnu++98/gnu++03 | gnu++11 | gnu++14 | gnu++17 |
7.5 ~ 5.5 | c++98/c++03 | c++11 | c++14 | c++1z(部分支持) | gnu++98/gnu++03 | gnu++11 | gnu++14 | gnu++1z(部分支持) |
4.9.4 ~ 4.8.5 | c++98/c++03 | c++11 | c++1y(部分支持) | gnu++98/gnu++03 | gnu++11 | gnu++1y(部分支持) | ||
4.7.4 | c++98 | c++11(部分支持) | gnu++98 | gnu++11(部分支持) | ||||
4.6.4 | c++98 | c++0x(部分支持) | gnu++98 | gnu++0x(部分支持) | ||||
4.5.4 | c++98 | c++0x(部分支持) | gnu++98 | gnu++0x(部分支持) |
gcc/g++ 指令选项 | 功 能 |
---|---|
-E(大写) | 预处理指定的源文件,不进行编译。 |
-S(大写) | 编译指定的源文件,但是不进行汇编。 |
-c | 编译、汇编指定的源文件,但是不进行链接。 |
-o | 指定生成文件的文件名 。 |
-llibrary(-I library) | 其中 library 表示要搜索的库文件的名称 。该选项用于手动指定链接环节中程序可以调用的库文件。建议 -l 和库文件名之间不使用空格 ,比如 -lstdc++。 |
-ansi | 对于 C 语言程序来说,其等价于 -std=c90;对于 C++ 程序来说,其等价于 -std=c++98。 |
-std= | 手动指令编程语言所遵循的标准,例如 c89、c90、c++98、c++11 等。 |
//存储在 demo.c 文件中
#include
int main(){
puts("GCC教程:http://c.xinbaoku.com/gcc/");
return 0;
}
gcc demo.c -o a.out
./a.out
//位于 demo.cpp 文件中
#include
using namespace std;
int main(){
cout << "GCC教程:http://c.xinbaoku.com/gcc/" << endl;
return 0;
}
g++ demo.cpp #或者 gcc -xc++ -lstdc++ -shared-libgcc demo.cpp
展开所有的宏定义
处理条件编译指令
处理 include 预编译
指令
删除注释
添加行号以及文件名标识
但是会保留 #progma 编译器指令
#include
int main(void){
puts("hello world");
return 0;
}
#默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。因此该指令往往会和 -o 选项连用,将结果导入到指令的文件中
# -C 选项,阻止 GCC 删除源文件和头文件中的注释
gcc -E demo.c -o demo.i
gcc -E -C demo.c -o demo.i
进行词法分析,语法分析,语义分析
词法分析 — 利用一个扫描器,将字符序列分割成一个个记号(例如:array 标识符,[ 左方括号)
语法分析 — 产生语法树,检测语法
语义分析 — 在语法树的基础上,加上类型,加隐式转换的节点,再次进行分析
进行代码优化
删除多余的运算
把可以在循环外面所执行的代码提出
将强度大的运算换算成小的运算
,例如将乘法转化为加法变换循环的控制条件
,可以将原本不必要的变量直接在循环内部剔除删除无用赋值
产生相应的汇编代码源文件
gcc -S demo.i -o test.i
gcc -S demo.c -fverbose-asm # 提高汇编代码的可读性
gcc -c main.s -o main.o
进行符号解析,分配虚拟地址空间
合并各个目标文件的.data段,.test段等等,重新调整偏移地址
符号表中的各个符号的唯一的定义
回过头来为这些符号填充上地址
#链接库名为 libm.a,并且位于 /usr/lib 目录,那么下面的命令会让 GCC 编译 main.c,然后将 libm.a 链接到 main.o:
gcc main.c -o main.out /usr/lib/libm.a
-L
选项,为 GCC 增加另一个搜索链接库的目录
:# 数学库的文件名是 libm.a。前缀 lib 和后缀.a 是标准的,m 是基本名称,GCC 会在 -l 选项后紧跟着的基本名称的基础上自动添加这些前缀、后缀,本例中,基本名称为 m。
gcc main.c -o main.out -L/usr/lib -lm
gcc myfun.c main.c -o main.exe
gcc *.c -o main.exe
demo项目
├─ headers
│ └─ test.h
└─ sources
├─ add.c
├─ sub.c
├─ div.c
└─ main.c
#直接编译
gcc main.c add.c sub.c div.c -o main.exe
#编译成静态链接库
# 1. 将所有指定的源文件,都编译成相应的目标文件:
gcc -c sub.c add.c div.c
# 2. 然后使用 ar 压缩指令,将生成的目标文件打包成静态链接库,其基本格式如下:ar rcs 静态链接库名称 目标文件 1 目标文件 2 ...
ar rcs libmymath.a add.o sub.o div.o
#使用编译的静态链接库
gcc -c main.c
gcc -static main.o libmymath.a # gcc main.o -static -L /root/demo/ -lmymath
#1) 直接使用源文件创建动态链接库,gcc -fpic -shared 源文件名... -o 动态链接库名
gcc -fpic -shared add.c sub.c div.c -o libmymath.so
#2) 先使用 gcc -c 指令将指定源文件编译为目标文件。仍以 demo 项目中的 add.c、sub.c 和 div.c 为例,先执行如下命令:
gcc -c -fpic add.c sub.c div.c
gcc -shared add.o sub.o div.o -o libmymath.so
#动态链接库使用
gcc main.c libmymath.so -o main.exe
ldd main.exe #查看当前文件在执行时需要用到的所有动态链接库,以及各个库文件的存储位置
- 将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
- 在终端输入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
,其中 xxx 为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效);- 修改~/.bashrc 或~/.bash_profile 文件,即在文件最后一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
(xxx 为动态库文件的绝对存储路径)。保存之后,执行source .bashrc
指令
链接操作
(生成可执行文件)时,GCC 可能会提示 “xxx:No such file or directory”(其中 xxx 表示查找失败的静态库或者动态库);执行
借助动态库生成的可执行文件时,GCC 可能会提示 “./main.exe: error while loading shared libraries: xxx.so: cannot open shared object file: No such file or directory”(其中 xxx 表示动态库的文件名)。#GCC 编译器只会在当前目录中(这里为 demo 目录)查找 libmymath.a 静态链接库;
gcc -static main.c libmymath.a -o main.exe
#使用 -l(小写的 L)选项指明了要查找的静态库的文件名,则 GCC 编译器会按照如下顺序,依次到指定目录中查找所需库文件:
# 如果 gcc 指令使用 -L 选项指定了查找路径,则 GCC 编译器会优先选择去该路径下查找所需要的库文件;
# 再到 Linux 系统中 LIBRARY_PATH 环境变量指定的路径中搜索需要的库文件;
# 最后到 GCC 编译器默认的搜索路径(比如 /lib、/lib64、/usr/lib、/usr/lib64、/usr/local/lib、/usr/local/lib64 等,不同系统环境略有差异)中查找。
gcc -static main.c -lmymath -o main.exe
当 GCC 编译器运行可执行文件时,会按照如下的路径顺序搜索所需的动态库文件:
-Wl,-rpath=dir
(其中 dir 表示要查找的具体路径,如果查找路径有多个,中间用:冒号分隔)选项指定动态库的搜索路径,则运行该文件时 GCC 会首先到指定的路径中查找所需的库文件; LD_LIBRARY_PATH 环境变量
指明的路径中查找所需的动态库文件; /ect/ld.so.conf 文件
中指定的搜索路径查找动态库文件; 该文件里面包含了若干自己编译的.so文件(例如 /lib、/lib64、/usr/lib、/usr/lib64 等)
中查找所需的动态库文件。注意,
可执行文件的当前存储路径,并不在默认的搜索路径范围内
,因此即便将动态库文件和可执行文件放在同一目录下,GCC 编译器也可能提示 “找不到动态库”。
- 将动态库文件的存储路径,添加到 LD_LIBRARY_PATH 环境变量中。假设动态库文件存储在 /usr 目录中,通知执行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr
指令,即可实现此目的(此方式仅在当前命令行窗口中有效);- 修改动态库文件的存储路径,即将其移动至 GCC 编译器默认的搜索路径中。
- 修改~/.bashrc 或~/.bash_profile 文件,即在文件最后一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
(xxx 为动态库文件的绝对存储路径)。保存之后,执行 source .bashrc 指令(此方式仅对当前登陆用户有效)。