2.2. 使用 GCC 构建代码
了解源代码必须转换为可执行代码的情况。
2.2.1. 代码表单之间的关系
先决条件
- 了解编译和链接的概念
可能的代码形式
C 和 C++ 语言具有三种代码:
用 C 或 C++ 语言编写的 源代码,以纯文本文件形式呈现。
文件通常会使用
.c
,.cc
,.cpp
,.h
,.hpp
,.i
,.inc
作为扩展名。有关支持的扩展及其解释的完整列表,请参阅 gcc 手册页:$ man gcc
对象代码(Object code),通过使用编译器对源代码进行编译来创建。这是一个中间形式。
对象代码文件使用
.o
扩展。可执行代码,通过带有一个 linker 的 linking 对象代码来创建。
Linux 应用程序可执行文件不使用任何文件名扩展。共享对象(库)可执行文件使用
.so
文件名扩展名。
还有用于静态链接的库归档文件。这是使用 .a
文件名扩展名的对象代码的变体。不建议使用静态链接。请参阅 第 2.3.2 节 “静态和动态链接”。
在 GCC 中处理代码形式
从源代码生成可执行代码分为两个步骤,需要不同的应用程序或工具。GCC 可用作编译器和链路器的智能驱动程序。这样,您可以使用一个单一的 gcc
命令用于任何必需的操作(编译和链接)。GCC 自动选择操作及其序列:
- 源文件是编译到对象文件中。
- 对象文件和库已链接(包括之前编译的源)。
可以运行 GCC 以便它只执行编译、只执行链接或在单个步骤中进行编译和链接。这由输入和请求的输出类型决定。
因为大型项目会需要一个构建系统,它通常会对每个操作独立运行 GCC,因此最好总是将编译和链接看做为两个不同的独立操作,即使 GCC 可以同时执行这两个操作。
2.2.2. 将源文件编译到对象代码
要从源文件而非可执行文件创建对象代码文件,必须指示 GCC 仅创建对象代码文件,作为其输出。此操作代表了更大项目的构建过程的基本操作。
先决条件
- C 或 C++ 源代码文件
- 在系统中安装了 GCC
步骤
- 更改到包含源代码文件的目录。
使用
-c
选项运行gcc
:$ gcc -c source.c another_source.c
创建对象文件,其文件名反映了原始源代码文件:
source.c
将生成source.o
。注意使用 C++ 源代码,将
gcc
命令替换为g++
以方便地处理 C++ 标准库依赖项。
2.2.3. 使用 GCC 启用 C 和 C++ 应用程序
由于调试信息较大,因此默认情况下不会包含在可执行文件中。要启用 C 和 C++ 应用的调试,您必须明确指示编译器创建它。
要启用在编译和链接代码时使用 GCC 创建调试信息,请使用 -g
选项:
$ gcc ... -g ...
-
由编译器和链接器执行的优化可能导致很难与原始源代码相关的执行代码:变量可能会被优化、循环展开、操作被合并到周围的操作中,等等。这会对调试有负面影响。为了改进调试体验,请考虑使用
-Og
选项设置优化。但是,更改优化等级会改变可执行代码,并可能会更改实际行为,包括删除一些错误。 -
要在调试信息中包含宏定义,请使用
-g3
选项而不是-g
。 -
-fcompare-debug
GCC 选项测试 GCC 使用调试信息和没有调试信息编译的代码。如果生成的两个二进制文件相同,则测试通过。此测试可确保可执行代码不受任何调试选项的影响,这会进一步确保调试代码中没有隐藏的错误。请注意,使用-fcompare-debug
选项会显著增加编译时间。有关这个选项的详情,请查看 GCC 手册页。
其他资源
- 使用 GNU Compiler Collection(GCC)- 用于调试程序的选项
- 使用 GDB 进行调试 - Debugging Information in Separate Files
GCC 手册页:
$ man gcc
2.2.4. GCC 的代码优化
可以将单个程序转换为多个计算机说明序列。如果您分配更多资源以在编译过程中分析代码,您可以实现更最佳的结果。
使用 GCC,您可以使用 -Olevel
选项设置优化级别。此选项接受一组值以代替 level。
级别 | 描述 |
---|---|
| 优化编译速度 - 无代码优化(默认)。 |
| 优化以提高代码执行速度(数量越大,速度越快)。 |
| 优化文件大小。 |
|
与 |
| 优化调试体验。 |
对于发行版本构建,请使用优化选项 -O2
。
在开发过程中,-Og
选项在某些情况下用于调试程序或库。因为有些程序错误清单只适用于特定的优化级别,所以使用发行版本优化级别测试程序或库。
GCC 提供大量选项来启用单个优化。如需更多信息,请参阅以下额外资源。
其他资源
- 使用 GNU Compiler Collection - 控制优化的选项
GCC 的 Linux 手册页:
$ man gcc
2.2.5. 使用 GCC 的强化代码选项
当编译器将源代码转换为对象代码时,它可以添加各种检查以防止经常利用的情况并提高安全性。选择正确的编译器选项可帮助生成更安全的程序和库,而无需更改源代码。
发行版本选项
对于针对 Red Hat Enterprise Linux 的开发人员,建议使用以下选项列表:
$ gcc ... -O2 -g -Wall -Wl,-z,now,-z,relro -fstack-protector-strong -fstack-clash-protection -D_FORTIFY_SOURCE=2 ...
-
对于程序,添加
-fPIE
和-pie
Position 独立可执行文件选项。 -
对于动态链接库,强制
-fPIC
(独立代码)选项间接增加安全性。
开发选项
使用以下选项检测开发过程中的安全漏洞。这些选项与发行版本选项一起使用:
$ gcc ... -Walloc-zero -Walloca-larger-than -Wextra -Wformat-security -Wvla-larger-than ...
其他资源
- 防御代码指南
- 使用 GCC 进行内存错误检测 - 红帽开发人员博客文章
2.2.6. 连接代码以创建可执行文件
连接是构建 C 或 C++ 应用程序时的最后一步。将所有对象文件和库链接到可执行文件中。
先决条件
- 一个个或多个对象文件
- 必须在系统上安装 GCC
步骤
- 更改到包含对象代码文件的目录。
运行
gcc
:$ gcc ... objfile.o another_object.o ... -o executable-file
从提供的对象文件和库中创建一个名为 executable-file 的可执行文件。
要链接附加库,请在对象文件列表后添加所需选项。
注意使用 C++ 源代码,将
gcc
命令替换为g++
以方便地处理 C++ 标准库依赖项。
2.2.7. 例如:使用 GCC 构建一个 C 程序(在一个步骤中编译和链接)
此示例显示构建简单示例 C 程序的确切步骤。
在本例中,编译和链接代码在一个步骤中完成。
先决条件
- 您必须使用 GCC。
步骤
创建一个目录
hello-c
并修改它:$ mkdir hello-c $ cd hello-c
使用以下内容创建文件
hello.c
:#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
使用 GCC 编译和链接代码:
$ gcc hello.c -o helloworld
这会编译代码,创建目标文件
hello.o
,并从目标文件链接可执行文件helloworld
。运行生成的可执行文件:
$ ./helloworld Hello, World!
2.2.8. 例如:使用 GCC 构建一个 C 程序(编译和连接在两个步骤中)
此示例显示构建简单示例 C 程序的确切步骤。
在本例中,编译和链接代码是两个独立的步骤。
先决条件
- 您必须使用 GCC。
步骤
创建一个目录
hello-c
并修改它:$ mkdir hello-c $ cd hello-c
使用以下内容创建文件
hello.c
:#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
使用 GCC 编译代码:
$ gcc -c hello.c
对象文件
hello.o
已创建。从对象文件中链接可执行文件
helloworld
:$ gcc hello.o -o helloworld
运行生成的可执行文件:
$ ./helloworld Hello, World!
2.2.9. 例如:使用 GCC 构建一个 C++ 程序(在一个步骤中编译和链接)
这个示例显示了构建示例最小 C++ 程序的确切步骤。
在本例中,编译和链接代码在一个步骤中完成。
先决条件
-
您必须了解
gcc
和g++
之间的区别。
步骤
创建一个目录
hello-cpp
并修改它:$ mkdir hello-cpp $ cd hello-cpp
使用以下内容创建文件
hello.cpp
:#include <iostream> int main() { std::cout << "Hello, World!\n"; return 0; }
使用
g++
编译和链接代码:$ g++ hello.cpp -o helloworld
这会编译代码,创建目标文件
hello.o
,并从目标文件链接可执行文件helloworld
。运行生成的可执行文件:
$ ./helloworld Hello, World!
2.2.10. 例如:使用 GCC 构建一个 C++ 程序(编译和连接在两个步骤中)
这个示例显示了构建示例最小 C++ 程序的确切步骤。
在本例中,编译和链接代码是两个独立的步骤。
先决条件
-
您必须了解
gcc
和g++
之间的区别。
步骤
创建一个目录
hello-cpp
并修改它:$ mkdir hello-cpp $ cd hello-cpp
使用以下内容创建文件
hello.cpp
:#include <iostream> int main() { std::cout << "Hello, World!\n"; return 0; }
使用
g++
编译代码:$ g++ -c hello.cpp
对象文件
hello.o
已创建。从对象文件中链接可执行文件
helloworld
:$ g++ hello.o -o helloworld
运行生成的可执行文件:
$ ./helloworld Hello, World!