Linux基础知识(11)- GCC 简单使用(一)| GCC 安装配置和 Makefile 的基本用法
GCC 的全拼为 GNU C Compiler,即 GUN 计划诞生的 C 语言编译器,显然最初 GCC 的定位确实只用于编译 C 语言。但经过这些年不断的迭代,GCC 的功能得到了很大的扩展,它不仅可以用来编译 C 语言程序,还可以处理 C++、Go、Objective -C 等多种编译语言编写的程序。与此同时,由于之前的 GNU C Compiler 已经无法完美诠释 GCC 的含义,所以其英文全称被重新定义为 GNU Compiler Collection,即 GNU 编译器套件。
所谓编译器,可以简单地将其理解为 “翻译器”。要知道,计算机只认识二进制指令(仅有 0 和 1 组成的指令),我们日常编写的 C 语言代码、C++ 代码、Go 代码等,计算机根本无法识别,只有将程序中的每条语句翻译成对应的二进制指令,计算机才能执行。
GCC 编译器从而停止过改进。截止 2020 年 5 月,GCC 已经从最初的 1.0 版本发展到了 10.1 版本,期间历经了上百个版本的迭代。作为一款最受欢迎的编译器,GCC 被移植到数以千计的硬件/软件平台上,几乎所有的 Linux 发行版也都默认安装有 GCC 编译器。GCC 支持的硬件平台(部分):
硬件 | 操作系统 |
Alpha | Red Hat Linux |
HPPA | HPUX |
Intel x86 | Debian Linux、Red Hat Linux 和 FreeBSD |
MIPS | IRIX |
PowerPC | AIX |
Sparc | Solaris |
1. GCC 安装配置
Linux 操作系统的自由、开源,在其基础上衍生出了很多不同的 Linux 操作系统,如 CentOS、Ubuntu、Debian 等。这些 Linux 发行版中,大多数都默认装有 GCC 编译器(版本通常都较低)。
1) CentOS 7.9 下安装
查看当前 GCC 版本:
$ gcc --version
-bash: gcc: command not found
注:表明当前系统没有安装 GCC 编译器。
(1) 基于 yum 安装
$ sudo yum -y install gcc gcc-c++
... Installed: gcc.x86_64 0:4.8.5-44.el7 gcc-c++.x86_64 0:4.8.5-44.el7 Dependency Installed: cpp.x86_64 0:4.8.5-44.el7 glibc-devel.x86_64 0:2.17-326.el7_9 glibc-headers.x86_64 0:2.17-326.el7_9 kernel-headers.x86_64 0:3.10.0-1160.80.1.el7 libmpc.x86_64 0:1.0.1-3.el7 libstdc++-devel.x86_64 0:4.8.5-44.el7 mpfr.x86_64 0:3.1.1-4.el7 Complete!
$ 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.
注:基于 yum 默认安装的 GCC 版本由 CentOS 的版本决定,由于 CentOS 7.9 下默认安装的 GCC 4.8.5 版本太低(低版本 GCC 默认的 C89 的标准不支持某些 C 语法,需要编译时指定 -std=语法标准),所以我们这里再手动安装更高版本的 GCC 。
(2) 手动安装
下载高版本源码安装包,安装前需要 CentOS 7.9 上已经安装低版本的 GCC,因为以源码的方式安装 GCC 编译器,即手动编译 GCC 编译器的源码,需要当前系统中存在一个可用的编译器。
GCC 下载地址:https://mirrors.aliyun.com/gnu/gcc/,这里下载 gcc-9.4.0.tar.gz 并复制到 ~/ 目录,具体安装过程如下:
$ cd ~/
# 解压至 /usr/local 目录下
$ sudo tar -vzxf gcc-9.4.0.tar.gz -C /usr/local
$ cd /usr/local/gcc-9.4.0
# 下载安装 GCC 所需要的依赖包(如 gmp、mpfr、mpc 等),
# 要确保这些依赖包被成功下载后,才能继续执行下面的安装步骤
$ sudo ./contrib/download_prerequisites
... gmp-6.1.0.tar.bz2: OK mpfr-3.1.4.tar.bz2: OK mpc-1.0.3.tar.gz: OK isl-0.18.tar.bz2: OK All prerequisites downloaded successfully.
# 手动创建一个目录,用于存放编译 GCC 源码包生成的文件
$ sudo mkdir build-9.4.0
$ cd build-9.4.0
# GCC 编译器支持多种编程语言的编译,配置 GCC 支持编译 C 和 C++ 语言 (其它语言:java、objc、obj-c++、go 等)
$ sudo ../configure --enable-checking=release --enable-languages=c,c++ --disable-multilib
# 使用 make 命令来编译和安装
$ sudo make & make install
$ gcc --version
gcc (GCC) 9.4.0 Copyright (C) 2019 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.
2) Ubuntu 20.04 下安装
查看当前 GCC 版本:
$ gcc --version
Command 'gcc' not found, but can be installed with:
sudo apt install gcc
注:表明当前系统没有安装 GCC 编译器。
基于 apt-get 安装:
$ sudo apt install gcc g++ make
... Setting up gcc-9 (9.4.0-1ubuntu1~20.04.1) ... Setting up gcc (4:9.3.0-1ubuntu2) ... Setting up g++-9 (9.4.0-1ubuntu1~20.04.1) ... Setting up g++ (4:9.3.0-1ubuntu2) ... Setting up make (4.2.1-1.2) ... update-alternatives: using /usr/bin/g++ to provide /usr/bin/c++ (c++) in auto mode Processing triggers for man-db (2.9.1-1) ... Processing triggers for libc-bin (2.31-0ubuntu9.9) ...
$ gcc -version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 Copyright (C) 2019 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.
注:Ubuntu 20.04 版本默认安装了 GCC 9.4.0,这里不需要手动安装更高版本的 GCC。
2. Hello World
使用 vim 编辑器,编写 C 和 C++ 程序,其功能是输出 "Hello World!"。如果 vim 编辑器没有安装,可以运行如下命令安装 vim:
$ sudo apt-get install vim # CentOS 下运行 sudo yum -y install vim
1) C 程序
$ cd ~/
$ vim hello.c
#include <stdio.h> int main() { puts("Hello World!- C"); return 0; }
# gcc 编译
$ gcc hello.c -o hello
$ ./hello
Hello World! - C
2) C++ 程序
$ vim hello2.cpp
#include <iostream> using namespace std; int main() { cout << "Hello World! - C++" << endl; return 0; }
# g++ 编译
$ g++ hello2.cpp -o hello2
$ ./hello2
Hello World! - C++
3) 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 等。 |
注:以上仅列出的一些常用指令选项,GCC 编译器提供有大量的指令选项,更多的指令选项,可以查看 GCC 手册 。
3. Makefile 的基本用法
上文我们使用 “gcc hello.c -o hello” 命令把 hello.c 源文件编译成了 hello 可执行程序(类似 Windows 下的 *.exe 文件)。
编译一个 *.c 文件(这里没有用到 *.h 头文件),可以用单命令行的方式很简单的实现,如果项目中有很多个 *.c 文件和 *.h 头文件,用单命令行方式编译,视乎是一件不可思议的事情。
要解决这个问题,最好的方式就是把项目的编译规则写下来,让编译器自动加载该规则进行编译,也就是使用 make 命令和 Makefile 文件。关于 make 命令和 Makefile 文件,具体描述如下:
make 命令:它可以帮助我们找出项目里面修改变更过的文件,并根据依赖关系,找出受修改影响的其他相关文件,然后对这些文件按照规则进行单独的编译,避免重新编译项目的所有的文件;
Makefile 文件:上面提到的规则、依赖关系就定义在这个 Makefile 文件中,定义文件的依赖关系之后,make 命令就能精准地进行编译工作;
注:GNU 官方的 make 说明文档:https://www.gnu.org/software/make/manual
1) Makefile 示例
创建 Makefile 文件,内容如下:
# target_a 是第一个目标,是最终目标,即 make 的默认目标 # 执行 ls 命令列出当前目录下的内容,target_a 依赖于 target_b 和 target_c target_a: target_b target_c ls # 执行 touch 命令创建 test.txt 文件,target_b 无依赖 target_b: touch test.txt # 执行 pwd 命令显示当前路径,target_c 无依赖 target_c: pwd # 执行 rm 命令删除 test.txt 文件,target_d 无依赖 target_d: rm -f test.txt
注:命令行之前是 tab 键,不能是空格。vim 的默认 tab 是 8 个空格,建议创建或修改 vim 的配置文件 ~/.vimrc,添加:
set tabstop=4 // 设置 tab 键是 4 个空格
set noexpandtab // 不把 tab 键用空格代替
运行:
$ ls
hello hello2 hello2.cpp hello.c Makefile
$ make
touch test.txt
pwd
/home/xxx
ls
hello hello2 hello2.cpp hello.c Makefile test.txt
$ make target_d
rm -f test.txt
$ make target_b
touch test.txt
$ make target_c
pwd
/home/xxx
注:target_d 不是默认目标,且不被其它任何目标依赖,所以直接 make 时 target_d并没有被执行,可以使用 “make 目标名” 的语法执行指定的目标。
make 命令和 Makefile 文件一起配合使用,从原理上讲有点类似于 Windows 的批处理程序 (*.bat)。
2) 使用 Makefile 编译程序
(1) 创建多个文件
创建 hello_main.c 文件,内容如下:
#include "hello_func.h" int main() { hello_func(); return 0; }
创建 hello_func.c 文件,内容如下:
#include <stdio.h> #include "hello_func.h" void hello_func(void) { printf("Hello world - Makefile!\n"); for (int i=0; i<3; i++ ) { printf("output i=%d\n",i); } }
创建 hello_func.h 文件,内容如下:
void hello_func(void);
(2) 命令行编译
# 注意最后的 "-I ." 包含名点 "."
$ gcc -o hello_main hello_main.c hello_func.c -I .
# 运行生成的 hello_main 程序
$ ./hello_main
Hello world - Makefile!
output i=0
output i=1
output i=2
注:低版本的 gcc 默认使用的 C89 的标准不支持 for 中定义循环变量,需要增加 -std=c99 或 -std=gun99 参数才能编译通过。比如:
gcc -o hello_main hello_main.c hello_func.c -std=gnu99 -I .
(3) Makefile 编译
创建 Makefile 文件,内容如下:
# 默认目标,hello_main 依赖于 hello_main.c 和 hello_func.c hello_main: hello_main.c hello_func.c gcc -o hello_main hello_main.c hello_func.c -I . # clean目标,删除编译生成的中间文件 clean: rm -f *.o hello_main
运行:
$ make
gcc -o hello_main hello_main.c hello_func.c -I .
$ ./hello_main
Hello world - Makefile!
output i=0
output i=1
output i=2
# 再次 make,会提示 hello_main 文件已是最新
$ make
make: `hello_main' is up to date.
# 使用 touch 命令更新一下 hello_func.c 的时间
$ touch hello_func.c
# 再次 make,由于 hello_func.c 比 hello_main 新,所以会再编译
$ make
gcc -o hello_main hello_main.c hello_func.c -I .
3) Makefile 语法
语法格式:
[目标1]:[依赖]
[命令1]
[命令2]
[目标2]:[依赖]
[命令1]
[命令2]
说明:
(1) 目标:指 make 要做的事情,可以是一个简单的代号,也可以是目标文件,需要顶格书写,前面不能有空格或 Tab。一个 Makefile 可以有多个目标,写在最前面的第一个目标,会被 Make 程序确立为 “默认目标”,例如前面的 target_a、hello_main;
(2) 依赖:要达成目标需要依赖的某些文件或其它目标。例如前面的 target_a 依赖于 target_b 和target_c,又如在编译的例子中,hello_main 依赖于 hello_main.c、hello_func.c 源文件,若这些文件更新了会重新进行编译;
(3) 命令1、命令2 … 命令n:make 达成目标所需要的命令。只有当目标不存在或依赖文件的修改时间比目标文件还要新时,才会执行命令。要特别注意命令的开头要用 “Tab” 键,不能使用空格代替,有的编辑器会把 Tab 键自动转换成空格导致出错,若出现这种情况请检查自己的编辑器配置。