Red Hat Training

A Red Hat training course is available for RHEL 8

第 2 章 创建 C 或 C++ 应用程序

2.1. 使用 GCC 构建代码

了解源代码必须转换为可执行代码的情况。

2.1.1. 代码表单之间的关系

先决条件

  • 了解编译和链接的概念

可能的代码形式

C 和 C++ 语言有三种形式的代码:

  • 用 C 或 C++ 语言编写的 源代码,以纯文本文件形式呈现。

    文件通常使用如 .c,.cc,.cpp,.h,.hpp,.i,.inc 的扩展名。有关支持的扩展及其解释的完整列表,请查看 gcc 手册页:

    $ man gcc
  • 目标代码,是使用 编译器 编译 源代码创建的。这是一种中间形式。

    目标代码文件使用 .o 扩展名。

  • 可执行代码,通过带有一个 linkerlinking 对象代码来创建。

    Linux 应用程序可执行文件不使用任何文件名扩展名。共享目标(library)可执行文件使用 .so 文件名扩展名。

注意

也存在用于静态链接的库存档文件。这是使用 .a 文件名扩展名的目标代码的变体。不建议使用静态链接。请参阅 第 2.2.2 节 “静态和动态链接”

GCC 中代码表单的处理

从源代码生成可执行代码分为两步执行,这需要不同的应用或工具。GCC 可用作编译器和链接器的智能驱动程序。这允许您将单个 gcc 命令用于任何必要的操作(编译和链接)。GCC 自动选择操作及其顺序:

  1. 源文件编译成目标文件。
  2. 目标文件和库被链接(包括之前编译的源)。

可以运行 GCC 以便它只执行编译、只执行链接或在单个步骤中进行编译和链接。这由输入的类型和请求的输出类型来决定的。

由于较大的项目需要一个通常为每个操作单独运行 GCC 的构建系统,因此最好始终考虑将编译和链接作为两个不同的操作,即使 GCC 可以同时执行这两项操作。

2.1.2. 将源文件编译成目标代码

要从源代码文件而不是可执行文件立即创建目标代码文件,必须指示 GCC 仅将目标代码文件创建为其输出。此操作代表了大型项目的构建过程的基本操作。

先决条件

流程

  1. 进到包含源代码文件的目录。
  2. 使用 -c 选项运行 gcc

    $ gcc -c source.c another_source.c

    创建目标文件,其文件名反了映原始源代码文件: source.c 会生成 source.o

    注意

    使用 C++ 源代码,将 gcc 命令替换为 g++,以方便处理 C++ 标准库依赖项。

2.1.3. 使用 GCC 启用 C 和 C++ 应用程序的调试

由于调试信息较大,因此默认情况下,不会包含在可执行文件中。要用它启用 C 和 C++ 应用程序的调试,您必须明确指示编译器创建它。

要在编译和链接代码时使用 GCC 启用调试信息的创建,请使用 -g 选项:

$ gcc ... -g ...
  • 由编译器和链接器执行的优化可能会产生难以与原始源代码相关的可执行代码:变量可能被优化、循环被展开、操作被合并到周围操作中,等等。这会对调试产生负面影响。为提高调试体验,请考虑使用 -Og 选项设置优化。但是,更改优化级别会更改可执行代码,并可能会改变实际d 行为,包括删除一些 bug。
  • 要在调试信息中也包含宏定义,请使用 -g3 选项,而不是 -g 选项。
  • -fcompare-debug GCC 选项测试使用带有调试信息的 GCC 编译的代码,而无需调试信息。如果生成的两个二进制文件相同,则测试通过。此测试确保可执行代码没有受到任何调试选项的影响,这进一步确保调试代码中没有隐藏的 bug。请注意,使用 -fcompare-debug 选项会显著增加编译时间。有关这个选项的详情,请查看 GCC 手册页。

其他资源

2.1.4. 使用 GCC 进行代码优化

一个程序可以转换为多个计算机指令序列。如果在编译过程中分配更多资源来分析代码,您可以获得更优的结果。

有了 GCC,您可以使用 -Olevel 选项设置优化级别。这个选项接受一组值来代替 level

级别描述

0

编译速度的优化 - 无代码优化(默认)。

1, 2, 3

优化以加快代码执行速度(数量越多,速度越快)。

s

文件大小的优化。

fast

与级别 3 设置一样,fast 忽略严格的标准合规性,以允许额外的优化。

g

调试体验的优化。

对于版本构建,请使用优化选项 -O2

在开发过程中,-Og 选项在某些情况下对于调试程序或库很有用。由于某些 bug 只在某些优化级别上出现,因此请使用版本优化级别测试程序或库。

GCC 提供了大量选项来启用单个优化。如需更多信息,请参阅以下额外资源。

其他资源

2.1.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 位置独立可执行文件选项。
  • 对于动态链接库,强制 -fPIC (Position Independent Code)选项会间接提高安全性。

开发选项

使用以下选项来检测开发过程中的安全漏洞:使用这些选项与发行版本的选项相结合:

$ gcc ... -Walloc-zero -Walloca-larger-than -Wextra -Wformat-security -Wvla-larger-than ...

其他资源

2.1.6. 链接代码以创建可执行文件

构建 C 或 C++ 应用程序时,链接是最后一步。链接将所有目标文件和库合并到一个可执行文件中。

先决条件

流程

  1. 进到包含目标代码文件的目录。
  2. 运行 gcc

    $ gcc ... objfile.o another_object.o ... -o executable-file

    从提供的目标文件和库创建一个名为 executable-file的可执行文件。

    要链接其他库,请在目标文件列表后添加所需的选项。如需更多信息,请参阅 第 2.2 节 “将库与 GCC 一起使用”

    注意

    使用 C++ 源代码,将 gcc 命令替换为 g++,以方便处理 C++ 标准库依赖项。

2.1.7. Example:使用 GCC 构建一个 C 程序(在一个步骤中编译和链接)

此示例演示了构建一个简单示例 C 程序的确切步骤。

在本例中,编译和链接代码在一个步骤中完成。

先决条件

  • 您必须了解如何使用 GCC。

流程

  1. 创建一个目录 hello-c ,并进到其中:

    $ mkdir hello-c
    $ cd hello-c
  2. 创建包含以下内容的文件 hello.c

    #include <stdio.h>
    
    int main() {
      printf("Hello, World!\n");
      return 0;
    }
  3. 使用 GCC 编译和链接代码:

    $ gcc hello.c -o helloworld

    这会编译代码,创建目标文件 hello.o,并从目标文件链接可执行文件 helloworld

  4. 运行生成的可执行文件:

    $ ./helloworld
    Hello, World!

2.1.8. Example:使用 GCC 构建一个 C 程序(编译和连接在两个步骤中)

此示例演示了构建一个简单示例 C 程序的确切步骤。

在本例中,编译和链接代码是两个独立的步骤。

先决条件

  • 您必须了解如何使用 GCC。

流程

  1. 创建一个目录 hello-c ,并进到其中:

    $ mkdir hello-c
    $ cd hello-c
  2. 创建包含以下内容的文件 hello.c

    #include <stdio.h>
    
    int main() {
      printf("Hello, World!\n");
      return 0;
    }
  3. 使用 GCC 编译代码:

    $ gcc -c hello.c

    目标文件 hello.o 已创建。

  4. 从目标文件链接可执行文件 helloworld:

    $ gcc hello.o -o helloworld
  5. 运行生成的可执行文件:

    $ ./helloworld
    Hello, World!

2.1.9. Example:使用 GCC 构建一个 C++ 程序(在一个步骤中编译和链接)

本例显示了构建最小 C++ 程序的确切步骤。

在本例中,编译和链接代码在一个步骤中完成。

先决条件

  • 您必须了解 gccg++ 之间的区别。

流程

  1. 创建 hello-cpp 目录,并进到其中:

    $ mkdir hello-cpp
    $ cd hello-cpp
  2. 创建包含以下内容的文件 hello.cpp

    #include <iostream>
    
    int main() {
      std::cout << "Hello, World!\n";
      return 0;
    }
  3. 使用 g++ 编译和链接代码:

    $ g++ hello.cpp -o helloworld

    这会编译代码,创建目标文件 hello.o,并从目标文件链接可执行文件 helloworld

  4. 运行生成的可执行文件:

    $ ./helloworld
    Hello, World!

2.1.10. Example:使用 GCC 构建一个 C++ 程序(编译和连接在两个步骤中)

本例显示了构建最小 C++ 程序的确切步骤。

在本例中,编译和链接代码是两个独立的步骤。

先决条件

  • 您必须了解 gccg++ 之间的区别。

流程

  1. 创建 hello-cpp 目录,并进到其中:

    $ mkdir hello-cpp
    $ cd hello-cpp
  2. 创建包含以下内容的文件 hello.cpp

    #include <iostream>
    
    int main() {
      std::cout << "Hello, World!\n";
      return 0;
    }
  3. 使用 g++ 编译代码:

    $ g++ -c hello.cpp

    目标文件 hello.o 已创建。

  4. 从目标文件链接可执行文件 helloworld:

    $ g++ hello.o -o helloworld
  5. 运行生成的可执行文件:

    $ ./helloworld
    Hello, World!