GNU C 编译器的程序员入门指南
创始人
2024-03-02 11:20:50
0

带你一窥生成二进制文件步骤的幕后,以便在出现一些错误时,你知道如何逐步解决问题。

C 语言广为人知,深受新老程序员的好评。使用 C 语言编写的源文件代码,使用了标准的英语术语,因而人们可以方便阅读。然而,计算机只能理解二进制代码。为将代码转换为机器语言,你需要使用一种被称为 编译器 compiler 的工具。

最常见的编译器是 GCC( GNU 编译器集 GNU Compiler Collection )。编译过程涉及到一系列的中间步骤及相关工具。

安装 GCC

为验证在你的系统上是否已经安装了 GCC,使用 gcc 命令:

$ gcc --version

如有必要,使用你的软件包管理器来安装 GCC。在基于 Fedora 的系统上,使用 dnf

$ sudo dnf install gcc libgcc

在基于 Debian 的系统上,使用 apt

$ sudo apt install build-essential

在安装后,如果你想查看 GCC 的安装位置,那么使用:

$ whereis gcc

演示使用 GCC 来编译一个简单的 C 程序

这里有一个简单的 C 程序,用于演示如何使用 GCC 来编译。打开你最喜欢的文本编辑器,并在其中粘贴这段代码:

// hellogcc.c
#include 

int main() {
    printf("Hello, GCC!\n");
    return 0;
}

保存文件为 hellogcc.c ,接下来编译它:

$ ls
hellogcc.c

$ gcc hellogcc.c

$ ls -1
a.out
hellogcc.c

如你所见,a.out 是编译后默认生成的二进制文件。为查看你所新编译的应用程序的输出,只需要运行它,就像你运行任意本地二进制文件一样:

$ ./a.out
Hello, GCC!

命名输出的文件

文件名称 a.out 是非常莫名其妙的,所以,如果你想具体指定可执行文件的名称,你可以使用 -o 选项:

(LCTT 译注:注意这和最近 Linux 内核废弃的 a.out 格式无关,只是名字相同,这里生成的 a.out 是 ELF 格式的 —— 也不知道谁给起了个 a.out 这破名字,在我看来,默认输出文件名就应该是去掉了 .c 扩展名后的名字。by wxy)

$ gcc -o hellogcc hellogcc.c

$ ls
a.out hellogcc hellogcc.c

$ ./hellogcc
Hello, GCC!

当开发一个需要编译多个 C 源文件文件的大型应用程序时,这种选项是很有用的。

在 GCC 编译中的中间步骤

编译实际上有四个步骤,即使在简单的用例中 GCC 自动执行了这些步骤。

  1. 预处理 Pre-Processing :GNU 的 C 预处理器(cpp)解析头文件(#include 语句),展开 宏 macros 定义(#define 语句),并使用展开的源文件代码来生成一个中间文件,如 hellogcc.i
  2. 编译 Compilation :在这个期间中,编译器将预处理的源文件代码转换为指定 CPU 架构的汇编代码。由此生成是汇编文件使用一个 .s 扩展名来命名,如在这个示例中的 hellogcc.s
  3. 汇编 Assembly :汇编程序(as)将汇编代码转换为目标机器代码,放在目标文件中,例如 hellogcc.o
  4. 链接 Linking :链接器(ld)将目标代码和库代码链接起来生成一个可执行文件,例如 hellogcc

在运行 GCC 时,可以使用 -v 选项来查看每一步的细节:

$ gcc -v -o hellogcc hellogcc.c

Compiler flowchart

手动编译代码

体验编译的每个步骤可能是很有用的,因此在一些情况下,你不需要 GCC 完成所有的步骤。

首先,除源文件文件以外,删除在当前文件夹下生成的文件。

$ rm a.out hellogcc.o

$ ls
hellogcc.c

预处理器

首先,启动预处理器,将其输出重定向为 hellogcc.i

$ cpp hellogcc.c > hellogcc.i

$ ls
hellogcc.c hellogcc.i

查看输出文件,并注意一下预处理器是如何包含头文件和扩展宏中的源文件代码的。

编译器

现在,你可以编译代码为汇编代码。使用 -S 选项来设置 GCC 只生成汇编代码:

$ gcc -S hellogcc.i

$ ls
hellogcc.c hellogcc.i hellogcc.s

$ cat hellogcc.s

查看汇编代码,来看看生成了什么。

汇编

使用你刚刚所生成的汇编代码来创建一个目标文件:

$ as -o hellogcc.o hellogcc.s

$ ls
hellogcc.c hellogcc.i hellogcc.o hellogcc.s

链接

要生成一个可执行文件,你必须将对象文件链接到它所依赖的库。这并不像前面的步骤那么简单,但它却是有教育意义的:

$ ld -o hellogcc hellogcc.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: hellogcc.o: in function `main`:
hellogcc.c:(.text+0xa): undefined reference to `puts'

在链接器查找完 libc.so 库后,出现一个引用 undefined puts 错误。你必须找出适合的链接器选项来链接必要的库以解决这个问题。这不是一个小技巧,它取决于你的系统的布局。

在链接时,你必须链接代码到 核心运行时 core runtime (CRT)目标,这是一组帮助二进制可执行文件启动的子例程。链接器也需要知道在哪里可以找到重要的系统库,包括 libclibgcc,尤其是其中的特殊的开始和结束指令。这些指令可以通过 --start-group--end-group 选项来分隔,或者使用指向 crtbegin.ocrtend.o 的路径。

这个示例使用了 RHEL 8 上的路径,因此你可能需要依据你的系统调整路径。

$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
    -o hello \
    /usr/lib64/crt1.o /usr/lib64/crti.o \
    --start-group \
        -L/usr/lib/gcc/x86_64-redhat-linux/8 \
        -L/usr/lib64 -L/lib64 hello.o \
        -lgcc \
        --as-needed -lgcc_s \
        --no-as-needed -lc -lgcc \
    --end-group \
    /usr/lib64/crtn.o

在 Slackware 上,同样的链接过程会使用一组不同的路径,但是,你可以看到这其中的相似之处:

$ ld -static -o hello \
    -L/usr/lib64/gcc/x86_64-slackware-linux/11.2.0/ \
    /usr/lib64/crt1.o /usr/lib64/crti.o hello.o /usr/lib64/crtn.o \
    --start-group \
        -lc -lgcc -lgcc_eh \
    --end-group

现在,运行由此生成的可执行文件:

$ ./hello
Hello, GCC!

一些有用的实用程序

下面是一些帮助检查文件类型、 符号表 symbol tables 和链接到可执行文件的库的实用程序。

使用 file 实用程序可以确定文件的类型:

$ file hellogcc.c
hellogcc.c: C source, ASCII text

$ file hellogcc.o
hellogcc.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

$ file hellogcc
hellogcc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb76b241d7d00871806e9fa5e814fee276d5bd1a, for GNU/Linux 3.2.0, not stripped

对目标文件使用 nm 实用程序可以列出 符号表 symbol tables :

$ nm hellogcc.o
0000000000000000 T main
             U puts

使用 ldd 实用程序来列出动态链接库:

$ ldd hellogcc
linux-vdso.so.1 (0x00007ffe3bdd7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f223395e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2233b7e000)

总结

在这篇文章中,你了解到了 GCC 编译中的各种中间步骤,和检查文件类型、 符号表 symbol tables 和链接到可执行文件的库的实用程序。在你下次使用 GCC 时,你将会明白它为你生成一个二进制文件所要做的步骤,并且当出现一些错误时,你会知道如何逐步处理解决问题。


via: https://opensource.com/article/22/5/gnu-c-compiler

作者:Jayashree Huttanagoudar 选题:lkxed 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

相关内容

华为仓颉编程语言首个 LT...
IT之家 7 月 1 日消息,华为仓颉编程语言首个 LTS 版本 ...
2025-07-01 21:12:49
VS2022配置工程的编译...
VS2022配置工程的编译路径(输出目录和中间目录&...
2025-06-01 19:24:39
编译原理个人作业--第二章
第二章 6 文法G6G_6G6​为 N→D∣NDN\rightar...
2025-05-31 13:52:40
Ubuntu20.4配置a...
Ubuntu20.4配置arm交叉编译环境 我是在虚拟机中配置的&...
2025-05-31 12:39:52
关于导入、编译、烧录S32...
问题来自于实践。 在学习了EB关于相关模块的配置之后,...
2025-05-31 10:37:52
【UNIX 环境编程】GC...
💭 写在前面:本文将介绍如何使用 G...
2025-05-31 02:18:08

热门资讯

Helix:高级 Linux ... 说到 基于终端的文本编辑器,通常 Vim、Emacs 和 Nano 受到了关注。这并不意味着没有其他...
使用 KRAWL 扫描 Kub... 用 KRAWL 脚本来识别 Kubernetes Pod 和容器中的错误。当你使用 Kubernet...
JStock:Linux 上不... 如果你在股票市场做投资,那么你可能非常清楚投资组合管理计划有多重要。管理投资组合的目标是依据你能承受...
通过 SaltStack 管理... 我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)...
Epic 游戏商店现在可在 S... 现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。...
《Apex 英雄》正式可在 S... 《Apex 英雄》现已通过 Steam Deck 验证,这使其成为支持 Linux 的顶级多人游戏之...
如何在 Github 上创建一... 学习如何复刻一个仓库,进行更改,并要求维护人员审查并合并它。你知道如何使用 git 了,你有一个 G...
2024 开年,LLUG 和你... Hi,Linuxer,2024 新年伊始,不知道你是否已经准备好迎接新的一年~ 2024 年,Lin...
什么是 KDE Connect... 什么是 KDE Connect?它的主要特性是什么?它应该如何安装?本文提供了基本的使用指南。科技日...
Opera 浏览器内置的 VP... 昨天我们报道过 Opera 浏览器内置了 VPN 服务,用户打开它可以防止他们的在线活动被窥视。不过...