Redian新闻
>
GNU C 编译器的程序员入门指南 | Linux 中国

GNU C 编译器的程序员入门指南 | Linux 中国

科技
 
导读:带你一窥生成二进制文件步骤的幕后,以便在出现一些错误时,你知道如何逐步解决问题。             
本文字数:4843,阅读时长大约:6分钟

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

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

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

安装 GCC

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

  1. $ gcc --version

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

  1. $ sudo dnf install gcc libgcc

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

  1. $ sudo apt install build-essential

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

  1. $ whereis gcc

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

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

  1. // hellogcc.c
  2. #include <stdio.h>
  3. int main() {
  4. printf("Hello, GCC!\n");
  5. return 0;
  6. }

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

  1. $ ls
  2. hellogcc.c
  3. $ gcc hellogcc.c
  4. $ ls -1
  5. a.out
  6. hellogcc.c

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

  1. $ ./a.out
  2. Hello, GCC!

命名输出的文件

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

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

  1. $ gcc -o hellogcc hellogcc.c
  2. $ ls
  3. a.out hellogcc hellogcc.c
  4. $ ./hellogcc
  5. 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 选项来查看每一步的细节:

  1. $ gcc -v -o hellogcc hellogcc.c

Compiler flowchart

手动编译代码

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

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

  1. $ rm a.out hellogcc.o
  2. $ ls
  3. hellogcc.c

预处理器

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

  1. $ cpp hellogcc.c > hellogcc.i
  2. $ ls
  3. hellogcc.c hellogcc.i

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

编译器

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

  1. $ gcc -S hellogcc.i
  2. $ ls
  3. hellogcc.c hellogcc.i hellogcc.s
  4. $ cat hellogcc.s

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

汇编

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

  1. $ as -o hellogcc.o hellogcc.s
  2. $ ls
  3. hellogcc.c hellogcc.i hellogcc.o hellogcc.s

链接

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

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

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

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

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

  1. $ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
  2. -o hello \
  3. /usr/lib64/crt1.o /usr/lib64/crti.o \
  4. --start-group \
  5. -L/usr/lib/gcc/x86_64-redhat-linux/8 \
  6. -L/usr/lib64 -L/lib64 hello.o \
  7. -lgcc \
  8. --as-needed -lgcc_s \
  9. --no-as-needed -lc -lgcc \
  10. --end-group \
  11. /usr/lib64/crtn.o

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

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

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

  1. $ ./hello
  2. Hello, GCC!

一些有用的实用程序

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

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

  1. $ file hellogcc.c
  2. hellogcc.c: C source, ASCII text
  3. $ file hellogcc.o
  4. hellogcc.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
  5. $ file hellogcc
  6. 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) :

  1. $ nm hellogcc.o
  2. 0000000000000000 T main
  3. U puts

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

  1. $ ldd hellogcc
  2. linux-vdso.so.1 (0x00007ffe3bdd7000)
  3. libc.so.6 => /lib64/libc.so.6 (0x00007f223395e000)
  4. /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中国 荣誉推出


LCTT 译者 :郑
🌟🌟🌟🌟🌟
翻译: 99.0 篇
|
贡献: 1274 天
2018-12-03
2022-05-30
https://linux.cn/lctt/robsean
欢迎遵照 CC-BY-SA 协议规定转载,
如需转载,请在文章下留言 “转载:公众号名称”,
我们将为您添加白名单,授权“转载文章时可以修改”。


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
如何从源码编译 GNOME Shell 和应用 | Linux 中国College Essay 系列 (十七) :文书主题的选择2022 (2)2个抖音工程师搞出新工具,意外风靡字节内部,项目经理用上安静多了,程序员不骗程序员我如何在 Linux 上扫描家庭照片 | Linux 中国手把手教你使用 GNU 调试器 | Linux 中国GNOME 新终端程序尝鲜 | Linux 中国有了扩展,GNOME Web 正逐渐成为 Linux 桌面上一个有吸引力的选择 | Linux 中国猫大 (4)散布敌国谣言。立此存照!值得尝试的六款 Linux 文字处理程序 | Linux 中国Linux 中国开通播客频道:“开源朗读者”和“硬核观察” | Linux 中国上一个说“丼”不读jǐng的人,已经被我骂哭了在 VirtualBox 安装 Arch Linux 的新手操作指南 | Linux 中国Ubuntu 22.04 LTS 中安装经典 GNOME Flashback 指南 | Linux 中国GNOME 43 中 Nautilus 文件管理器的 6 个新变化 | Linux 中国微信小程序反编译Linux 下的 Docker 入门教程 | Linux 中国实测 Linux Mint 升级工具 | Linux 中国编译代码时动态地链接库 | Linux 中国当下运行容器的 3 个步骤 | Linux 中国最适合程序员的 10 款 Linux 发行版 | Linux 中国春分时上阵风峰---Gusty peak在 Linux 中找到你的路由器的 IP 地址(默认网关) | Linux 中国2022 Rust 入门指南 | Linux 中国如何在 Linux 和 Windows 电脑之间共享文件 | Linux 中国现在的程序员,接offer看的不是薪资,而是……如何在 Linux 桌面中启用 “激活 Linux” 水印通知 | Linux 中国说俄罗斯的石油和天然气,你必须要明白的烤箱入门指南 | 预算内这些值得买!HydraPaper:一个支持多显示器的 Linux 壁纸管理器 | Linux 中国社区纠纷不断:程序员何苦为难程序员如何编写 C 程序在 Linux 上创建音乐播放列表 | Linux 中国硬核观察 #683 不值得使用 -O3 来编译 Linux 内核使用 dnf 进行 Linux 包管理 | Linux 中国Archinstall 新的菜单系统让安装 Arch Linux 更容易了 | Linux 中国
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。