当前位置: 首页 > news >正文

从源码到可执行文件:hello.c 的二进制之旅

文章目录

    • 0.引言
    • 1.整体介绍
    • 2.预编译
    • 3.编译
    • 4.汇编
    • 5.链接
    • 6.总结

0.引言

你有没有想过为什么编译后才能运行?编译器在这其中起到什么作用?生成的可执行文件里有什么?操作系统又是如何加载它的呢?本系列文章会随着各个章节展开来让读者对这些问题有清晰的认知,整体章节内容规划如下:

编译流程的整体认识(hello.c到a.out)
编译器的翻译流程(词法分析、语法树与优化详解)
目标文件的格式(.o文件解析,符号表、重定位与链接报错解析)
静态链接原理(理解为什么有的程序“打包后会很大)
动态链接原理(理解共享库(.so)如何实现“一次编译,到处运行”)
程序装载流程(理解操作系统如何加载它,在什么位置运行)
编译链接实战(段错误、符号冲突与性能优化以及交叉编译和LLVM)

本文是第一篇,将会介绍基础的概念,从宏观视角来看hello.c如何变成a.out。

1.整体介绍

我们先从简单的hello.c开始:

#include <stdio.h>  
int main() {  printf("Hello World\n");  return 0;  
}

编写好代码后我们只需要执行gcc hello.c就会在当前目录下生成一个可执行的a.out,虽然我们只是执行了一条命令,但其实际是经历了四大阶段(预编译、编译、汇编、链接),其中每一个阶段都有其自己的规则。

在这里插入图片描述

2.预编译

预编译是将源代码文件(hello.c)和相关的头文件(stdio.h以及其对应包含的头文件)编译生成一个.i文件,这一阶段主要处理源代码文件中的"#"开头的预编译指令,主要的规则如下,我们可以通过查看.i文件来检查一些宏定义是否正确展开或者头文件是否正确包含。

1)处理条件编译指令(如#if、#ifdef、#elif、#else、#endif)。

2)展开头文件(处理#include,将include的头文件内容插入指令位置,这个过程会递归展开)。

3)处理define(将#define进行展开)。

4)删除注释(包括//或者/*注释)。

5)保留#pragma指令(编译器后续使用)。

6)增加行号和文件名标识(出问题后用来作为报错信息)。

#可以使用gcc -E查看预处理后的结果,打开hello.i就能看到被展开的内容
gcc -E hello.c -o hello.i

3.编译

预编译可以看成是进行简单的替换和规则处理,编译的话则是要进行词法分析、语法分析、语义分析、代码优化和生成。此处做简单介绍,下一篇文章会做更为具体说明。

1)词法分析:将代码拆分成一个个的词法单元(Token)。

2)语法分析:根据语法规则,将Token组成抽象语法树(AST)。

3)语义分析:检查语义的正确性,只能进行静态的检查(如类型是否匹配,变量是否定义等)。

4)代码优化和生成:对AST进行优化(如常量折叠、循环展开等),生成汇编代码。

#使用gcc -S生成汇编指令(完成了预编译,编译)
gcc -S hello.c -o hello.s

其部分代码如下:
在这里插入图片描述

4.汇编

汇编就是将汇编代码(编译生成的代码)转换为机器可以执行的指令的过程,几乎每一个汇编指令都有一个对应的机器指令,也就是说汇编过程其实只需要一一翻译就可以了,汇编过程可以通过两个语句来进行生成:

as hello.s -o hello.o
gcc -c hello.s -o hello.o

生成的.o文件为二进制文件,我们可以使用objdump来查看,这个工具我们后面会经常用到。

objdump -d hello.ohello.o:     file format elf64-x86-64Disassembly of section .text:0000000000000000 <main>:0:   f3 0f 1e fa             endbr64 4:   55                      push   %rbp5:   48 89 e5                mov    %rsp,%rbp8:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # f <main+0xf>f:   e8 00 00 00 00          callq  14 <main+0x14>14:   b8 00 00 00 00          mov    $0x0,%eax19:   5d                      pop    %rbp1a:   c3                      retq   

5.链接

到链接这一步时我们已经得到了机器码程序,但是其还是不能直接运行,因为其缺少一些外部的依赖(比如printf的实现)。链接器会将多个目标文件组合成一个完整的可执行文件(分为静态链接和动态链接),其过程主要是符号解析(找到外部符号位置)和重定位(重设代码中地址),这个过程较为复杂,我们用单独的文章进行说明。

#完成链接,生成可执行程序
gcc hello.o -o a.out
#查看类型
file a.out 
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=13c23451a252ce49af91ee79c8bd4fa7821d86de, for GNU/Linux 3.2.0, not stripped

6.总结

本文从宏观视角介绍了hello.c到二进制可执行文件的过程,其分为四个阶段(也是计算机分层抽象的体现),每个阶段有自己的职责,让其可以高效且方便维护。下一篇我们将去深入编译过程,去了解代码具体怎么翻译成机器码,词法分析语法分析做的事情和代码优化的具体内容。

http://www.lryc.cn/news/621776.html

相关文章:

  • Python从入门到高手9.3节: 利用字典进行格式化
  • GoLand深度解析:智能开发利器与cpolar内网穿透方案的协同实践
  • 配置国内加速源后仍然无法拉取镜像
  • Vue2与Vue3生命周期函数全面解析:从入门到精通
  • 【自动驾驶】自动驾驶概述 ② ( 自动驾驶技术路径 | L0 ~ L5 级别自动驾驶 )
  • Linux软件编程(五)(exec 函数族、system、线程)
  • Unity导航寻路轨迹可视化实现
  • Unity_数据持久化_Json
  • Ubuntu DNS 综合配置与排查指南
  • 小程序上拉加载下一页数据
  • 基于HTML5与Tailwind CSS的现代运势抽签系统技术解析
  • GEO入门:什么是生成式引擎优化?它与SEO的根本区别在哪里?
  • 流处理、实时分析与RAG驱动的Python ETL框架:构建智能数据管道(中)
  • Fanuc机器人EtherCAT通讯配置详解
  • 【Linux基础知识系列】第九十六篇 - 使用history命令管理命令历史
  • 【机器人】人形机器人“百机大战”:2025年产业革命的烽火与技术前沿
  • Zabbix【部署 01】Zabbix企业级分布式监控系统部署配置使用实例(在线安装及问题处理)程序安装+数据库初始+前端配置+服务启动+Web登录
  • 在 Vue2 中使用 pdf.js + pdf-lib 实现 PDF 预览、手写签名、文字批注与高保真导出
  • 力扣习题:基本计算器
  • Spring 工具类:StopWatch
  • Java 泛型类型擦除
  • 【递归、搜索与回溯算法】DFS解决FloodFill算法
  • Pytest项目_day17(随机测试数据)
  • JUC学习笔记-----LongAdder
  • 2025年最新油管视频下载,附MassTube下载软件地址
  • 嵌入式 C 语言编程规范个人学习笔记,参考华为《C 语言编程规范》
  • 嵌入式硬件篇---电容串并联
  • 嵌入式硬件篇---电容滤波
  • flutter开发(二)检测媒体中的静音
  • Flinksql bug: Heartbeat of TaskManager with id container_XXX timed out.