Platform-specific options
GCC provides a range of platform-specific options for different types of CPUs. These options control features such as hardware floating-point modes, and the use of special instructions for different CPUs. They can be selected with the ‘-m’ option on the command line, and work with all the GCC language frontends, such as gcc and g++.
Intel and AMD x86 options
可以使用 GCC 平台特定的选项来控制广泛使用的 Intel 和 AMD x86 处理器系列(386、486、奔腾等)的功能。
在这些平台上,GCC 默认生成与 x86 系列所有处理器兼容的可执行代码 - 一直追溯到 386。但是,也可以针对特定处理器进行编译以获得更好的性能。Also referred to as targeting
a specific processor. 针对不同平台编译。
gcc -Wall -march=pentium4 hello.c
使用特定 -march=CPU
选项生成的代码将运行得更快,但无法在 x86 系列的其他处理器上运行。如果计划分发可执行文件以供在 Intel 和 AMD 处理器上通用使用,则应在不使用任何 -march
选项的情况下编译它们。
作为替代方案,-mcpu=CPU
选项在速度和可移植性之间提供了折衷方案——它生成的代码在指令调度方面针对特定处理器进行了调整,但不使用 x86 系列中其他 CPU 上不可用的任何指令。生成的代码将与所有 CPU 兼容,并且在 -mcpu
指定的 CPU 上具有速度优势。-mcpu
生成的可执行文件无法达到与 -march
相同的性能,但在实践中可能更方便。
AMD 已将 32 位 x86 指令集增强为 64 位指令集(称为 x86-64),该指令集已在其 AMD64 处理器中实现。在 AMD64 系统上,GCC 默认生成 64 位代码。选项 -m32
允许生成 32 位代码。
AMD64 处理器有几种不同的内存模型,用于在 64 位模式下运行的程序。默认模型是小代码模型,允许代码和数据大小最大为 2 GB。中等代码模型允许无限的数据大小,可以使用 -mcmodel=medium
进行选择。还有一个大代码模型,除了无限的数据大小外,还支持无限的代码大小。它目前尚未在 GCC 中实现,因为中等代码模型足以满足所有实际用途 — 实际中不会遇到大小大于 2 GB 的可执行文件。
为系统级代码(例如 Linux 内核)提供了特殊的内核代码模型 -mcmodel=kernel
。需要注意的一点是,默认情况下,在 AMD64 上,堆栈指针下方会分配一个 128 字节的内存区域用于临时数据,称为红区 red-zone,Linux 内核不支持该区域。在 AMD64 上编译 Linux 内核需要使用选项 -mcmodel=kernel -mno-red-zone
。
DEC Alpha options
DEC Alpha 处理器的默认设置可最大程度地提高浮点性能,但代价是完全支持 IEEE 算术功能。DEC Alpha 处理器的默认配置不支持无穷大算术和渐进下溢 infinities or underflows(非规范化数字)。产生无穷大或下溢的运算将生成浮点异常(也称为陷阱),并导致程序终止,除非操作系统捕获并处理异常(这通常效率低下)。IEEE 标准规定这些运算应生成特殊结果以表示 IEEE 数字格式的数量。
在大多数情况下,DEC Alpha 默认行为是可以接受的,因为大多数程序不会产生无穷大或下溢。对于需要这些功能的应用程序,GCC 提供了选项 -mieee
以完全支持 IEEE 算术。
1 |
|
在 IEEE 算术中,1/0 的结果是 inf(无穷大)。如果使用默认设置针对 Alpha 处理器编译程序,则会生成异常,从而终止程序:
1 | gcc -Wall alpha.c |
使用 -mieee
选项可确保完全符合 IEEE 标准 – 除法 1/0 正确产生结果 inf 并且程序继续成功执行:
1 | gcc -Wall -mieee alpha.c |
请注意,使用 -mieee
编译时,生成浮点异常的程序运行速度会更慢,因为异常是在软件而不是硬件中处理的。
Troubleshooting
故障排查。GCC provides several help and diagnostic options to assist in troubleshooting problems with the compilation process.
Help for command-line options
为了简要提醒各种命令行选项,GCC 提供了一个帮助选项,显示顶级 GCC 命令行选项的摘要:gcc --help
。
要显示 gcc 及其相关程序(例如 GNU Linker 和 GNU Assembler)的完整选项列表,请使用上面的帮助选项和详细(-v
)选项:gcc -v --help
。
此命令生成的完整选项列表非常长 - 可能希望使用 more 命令翻阅它,或将输出重定向到文件以供参考:gcc -v --help 2>&1 | more
。
Version numbers
gcc --version
or gcc -v
在调查编译问题时,版本号很重要,因为旧版本的 GCC 可能缺少程序使用的某些功能。版本号的格式为主要版本.次要版本或主要版本.次要版本.微版本 major-version.minor-version.micro-version,其中附加的第三个微版本号用于发布系列中的后续错误修复版本。
Verbose compilation
-v
选项还可用于显示有关用于编译和链接程序的命令的确切序列的详细信息。
gcc -v -Wall hello.c
当编译过程本身出现问题时,-v
生成的输出非常有用。它显示用于搜索头文件和库的完整目录路径、预定义的预处理器符号以及用于链接的目标文件和库。
Compiler-related tools
Creating a library with the GNU archiver
GNU 归档器将一组目标文件组合成一个归档文件,也称为库。归档文件只是一种将大量相关目标文件一起分发的便捷方式。
ar cr libhello.a hello_fn.o bye_fn.o
The option cr
stands for create and replace
.
The archiver ar also provides a table of contents
option t
to list the object files in an existing library:
1 | ar t libhello.a |
请注意,当分发一个库时,它提供的公共函数和变量的头文件也应该可用,以便最终用户可以包含它们并获得正确的原型。
gcc -Wall main.c libhello.a -o hello
or gcc -Wall -L. main.c -lhello -o hello
The option -L.
is needed to add the current directory to the library search path.
Using the profiler gprof
The GNU profiler gprof is a useful tool for measuring the performance of a program—it records the number of calls to each function and the amount of time spent there, on a per-function basis. 可以从 gprof 的输出中轻松识别出消耗大量运行时间的函数。加速程序的努力应该首先集中在那些占据整个运行时间的函数上。
To use profiling, the program must be compiled and linked with the -pg
profiling option:
1 | gcc -Wall -c -pg collatz.c |
这将创建一个经过检测的可执行文件,其中包含记录每个函数所花费时间的附加指令。
If the program consists of more than one source file then the -pg
option should be used when compiling each source file, and used again when linking the object files to create the final executable。
Forgetting to link with the option -pg
is a common error, which prevents profiling from recording any useful information.
运行编译后的程序后,会在当前目录生成 gmon.out
文件,可以通过将可执行文件的名称作为参数来用 gprof
进行分析:
1 | gprof a.out # 会读取生成的文件,输出统计信息 |
Coverage testing with gcov
GNU 覆盖测试工具 gcov
分析程序在运行期间每行代码的执行次数。这样就可以找到未使用或未在测试中执行的代码区域。当与 gprof
的分析信息相结合时,覆盖测试中的信息允许将加速程序的努力集中在源代码的特定行上。
To enable coverage testing the program must be compiled with the following options: gcc -Wall -fprofile-arcs -ftest-coverage cov.c
.
这将创建一个经过检测的可执行文件,其中包含记录程序每行执行次数的附加指令。选项 -ftest-coverage
添加了用于计算各行执行次数的指令,而 -fprofile-arcs
则包含程序每个分支的检测代码。分支检测通过 if
语句和其他条件记录了不同路径的采用频率。然后必须运行可执行文件以创建覆盖率数据。
执行程序后运行的数据被写入当前目录中的几个扩展名为 .bb
、.bbg
和 .da
的文件中。
这些数据使用 gcov 配合源代分析,上面的是基于可执行文件。gcov cov.c
The gcov command produces an annotated version of the original source file, with the file extension .gcov
, containing counts of the number of times each line was executed. 没有执行的行会被 ######
标记。
How the compiler works
本章详细介绍了 GCC 如何将源文件转换为可执行文件。编译是一个多阶段过程,涉及多个工具,包括 GNU 编译器本身(通过 gcc 或 g++ 前端)、GNU 汇编器 as 和 GNU 链接器 ld。编译过程中使用的完整工具集称为工具链。
An overview of the compilation process
The sequence of commands executed by a single invocation of GCC con-sists of the following stages:
- preprocessing (to expand macros)
- compilation (from source code to assembly language)
- assembly (from assembly language to machine code)
- linking (to create the final executable)
The preprocesssor
编译过程的第一阶段是使用预处理器来扩展宏和包含的头文件。为了执行此阶段,GCC 执行以下命令:cpp hello.c > hello.i
。结果是一个文件 hello.i
,其中包含扩展了所有宏的源代码。
按照惯例,预处理文件的文件扩展名是 .i
(对于 C 程序)和 .ii
(对于 C++ 程序)。实际上,除非使用 -save-temps
选项,否则预处理文件不会保存到磁盘。
The compiler
该过程的下一个阶段是将预处理源代码实际编译为特定处理器的汇编语言。命令行选项 -S
指示 gcc 将预处理的 C 源代码转换为汇编语言,而无需创建目标文件:gcc -Wall -S hello.i
。
The resulting assembly language is stored in the file hello.s
.
The assembler
The purpose of the assembler is to convert assembly language into machine code and generate an object file. When there are calls to external functions in the assembly source file, the assembler leaves the addresses of the external functions undefined, to be filled in later by the linker. The assembler can be invoked with the following command line: as hello.s -o hello.o
.
As with GCC, the output file is specified with the -o
option. The resulting file hello.o
contains the machine instructions for the Hello World program, with an undefined reference to printf
.
The linker
编译的最后阶段是链接目标文件以创建可执行文件。In practice, an executable requires many external functions from system and C run-time (crt) libraries.Consequently, the actual link commands used internally by GCC are complicated.
ld 命令很复杂,但是不需要我们关系,gcc 帮助链接目标文件。gcc hello.o
.
Examining compiled files
Identifying files
When a source file has been compiled to an object file or executable the options used to compile it are no longer obvious. The file command looks at the contents of an object file or executable and determines some of its characteristics, such as whether it was compiled with dynamic or static linking.
当源文件被编译为目标文件或可执行文件时,用于编译它的选项不再明显。file
命令查看目标文件或可执行文件的内容并确定它的某些特性,例如它是使用动态链接还是静态链接编译的。
1 | file a.out |
The output shows that the executable file is dynamically linked, and com- piled for the Intel 386 and compatible processors.
- ELF
The internal format of the executable file (ELF stands forExecutable and Linking Format
, other formats such as COFFCom-mon Object File Format
are used on some older operating systems (MS-DOS) - 32-bit
The word size (for some platforms this would be 64-bit). - LSB byte order
Compiled for a platform with least significant byte first word ordering, such as Intel and AMD x86 processors (the alternative MSB most significant byte first is used by other processors - Intel 80386
The processor the executable file was compiled for. - version 1 (SYSV)
This is the version of the internal format of the file. - dynamically linked
The executable uses shared libraries (statically linked indicates programs linked statically, for example using the-static
option) - not stripped
The executable contains a symbol table (this can be removed with the strip command).
The file command can also be used on object files, where it gives similar output.
Examining the symbol table
As described earlier in the discussion of debugging, executables and object files can contain a symbol table.This table stores the location of functions and variables by name, and can be displayed with the nm command:
1 | man nm |
Among the contents of the symbol table, the output shows that the start of the main function has the hexadecimal offset 080483f0. Most of the symbols are for internal use by the compiler and operating system. A T
in the second column indicates a function that is defined in the object file, while a U
indicates a function which is undefined (and should be resolved by linking against another object file). A complete explanation of the output of nm can be found in the GNU Binutils manual.
nm 命令最常见的用途是检查库是否包含特定函数的定义,通过在函数名称的第二列中查找“T”条目。
Finding dynamically linked libraries
When a program has been compiled using shared libraries it needs to load those libraries dynamically at run-time in order to call external functions. The command ldd examines an executable and displays a list of the shared libraries that it needs. These libraries are referred to as the shared library dependencies of the executable. ldd a.out
The ldd command can also be used to examine shared libraries them- selves, in order to follow a chain of shared library dependencies.