C语言可执行程序到底怎样生成?

简介: C语言可执行程序到底怎样生成?

C语言的程序到底是怎样生成的呢?又怎样去执行呢?我们来探索。本篇是讲解编译环境。


在ANSI C(标准C语言)的任何一种实现中,存在两个不同的环境。第一种是翻译环境,在这个环境中源代码.c被转换为可执行的机器指令.exe,第二种是执行环境,它用于实际执行代码。


test.c----->test.i------->test.s----->test.obj----->test.exe

程序的翻译环境

程序的翻译环境同时又分为两个阶段 编译链接  

组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。

每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。

链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

在windows环境下,生成相应的目标文件test.obj

在Linux环境下,生成的目标文件是test.o

test.c源文件---------->test.obj目标文件+链接库------------>test.exe可执行程序  

理解图:


以我们的VS编译器为例子。编译和链接依赖的是什么工具呢?


NO1.VS编译器工具

  • 那在我们的VS2022集成开发环境下,VS依赖就是cl.exe这个工具去实现编译这项功能。
  • 在windows环境下,生成相应的目标文件test.obj
  • 在Linux环境下,生成的目标文件是test.o


NO2.VS链接器工具

  • VS2022集成开发环境下,VS依赖就是cl.exe这个工具去实现链接这项功能。
  • 在不同的编译环境下,标准库中的实现肯定是有差异的。


NO3.链接库是什么?

#include<stdio.h>
int main()
{
  printf("hehe\n");
  return 0;
}
  • 例如 像上面的#includeprintf库函数 我们就直接使用,是因为放在了链接库中直接供给我们使用。标准库中 提供的 现成的库函数 都是以链接库的形式给我们提供的。

编译


其实编译本身也分为几个阶段:预编译(预处理)  编译 汇编 这三个阶段。我们接下来分别探索一下这三个阶段分别干了什么事情。我们整个过程都在 : Linux环境下,使用gcc编译器去验证整个过程。test.c------->test.i--------->test.s----->test.o(test.obj)


具体要用到的指令:


  1. 预处理 选项 gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
  2. 编译 选项 gcc -S test.c 编译完成之后就停下来,结果保存在test.s中。
  3. 汇编 gcc -c test.c 汇编完成之后就停下来,结果保存在test.o中。  

预处理


我们想要展示一个预处理的效果,那我们希望预处理之后这个代码就不会往后继续执行了。这里我们要用到一个gcc的指令: -E  test.c  / test.c -E预处理之后test.c代码不会往后继续执行

在我们VS验证之后我们发现我们预处理的文件名为test.i 所以这里在Linux环境下,我们使用gcc指令:-o test.i (test.c预处理完之后的文件命名为test.i,如果没有指定就直接打印在屏幕上了)

在观察了test.c和test.i两个文件差异我们发现预处理对源代码做了一些文本操作处理的。


  • 注释的替换(删除)。注释被替换成一个空格
  • 头文件的包含#include< >
  • #define符号的替换
  • #include和#define这种都是 预处理指令。所有的预处理指令都在预处理阶段处理的。

在我们的下篇博客我们也会详细去讲到预处理这个阶段的知识点。


注释的删除  

头文件的包含

编译

来到我们的编译阶段,我们想要展示一个编译的效果,那我们希望编译之后这个代码就不会往后继续执行了。这里我们要用到一个gcc的指令: -S  test.i  / test.i -S(编译之后test.i代码不会往后继续执行)


同时我们也可以使用gcc指令:-o test.s  (把test.i编译完之后的文件命名为test.s,如果没有指定也会生成test.s)


我们发现test.s里面放置的都是汇编代码。 其实编译的过程就是:把C语言代码翻译成了汇编代码。这个过程是非常非常复杂的。简单来说,编译这个过程包含:

  • 语法分析
  • 词法分析
  • 语义分析
  • 符号汇总


汇编


到这里,汇编语言依旧不能被我们计算机读懂,汇编语言必须经过汇编器转化成二进制指令,才能被计算机读懂。


我们想要展示一个汇编的效果,那我们希望汇编之后这个代码就不会往后继续执行了。这里我们要用到一个gcc的指令: -c  test.s  / test.s -c(汇编之后test.s代码不会往后执行)


同时我们也可以使用gcc指令:-o test.o(test.obj)  (把test.s汇编完之后的文件命名为test.o,如果没有指定也会生成test.o)


我们发现test.o里面放置的都是二进制信息代码。 其实汇编的过程就是:把汇编代码翻译成二进制指令(目标文件)。到此为止,计算机看得懂了。

  • 生成符号表

链接

现在我们的目标文件test.obj 在gcc指令下:gcc test.o(test.obj) -o test.c(test.exe) (test.o链接完之后的文件命名为test.c,如果没有指定也会生成test.c(其实就是test.exe可执行程序


  • 链接目标文件obj和链接库生成可执行程序(二进制程序)
  • 合并段表
  • 合并符号表和符号表的重新定位

符号表

上面在学习编译/汇编/链接这几个阶段,我们发现他们有一个公共的点。

  • 编译里有:符号汇总。
  • 汇编里面有:生成符号表。
  • 链接里面有:合并段表和符号表的合并和重定位。


这些功能的实现应用在多文件的工程的项目里。

那编译器是如何处理这种多文件的定义和声明的呢?下面我们来深入学习一下。

编译阶段符号汇总

当每个源文件经过编译器都会发生符号汇总。一般都是全局变量汇总,局部变量一般不会汇总。

为什么只有全局变量才会汇总,因为只有全局变量才会跨文件使用。所以一般只会汇总全局变量

在符号表汇总的时候,编译器会为每个文件的全局变量分配地址

此刻这个阶段每个.o目标文件都有自己的符号表。

汇编生成符号表

像上面的test.o和add.o文件都有自己的符号表。我们需要知道的是:

  • 在gcc编译器下,生成的目标和二进制的可执行文件都是按照 elf 这种文件的形式组织。
  • elf 会把目标文件分成不同的段,每个段来存放不同的数据。
  • 因为格式都是 elf 所以分段的格式都是一样。

链接合并段表和符号表

接下来,把相同段的目标文件的数据进行锁定和合并成二进制的可执行文件

合并完段表,我们就要进行符号表的合并和重新定位

当编译器在编译阶段想要去查找的时候,都是去符号表里面根据地址查找。

错误

当我们在执行程序的时候,经常发生这样的错误。❌❌


  • 第一种情况:这个函数根本不存在
  • 第二种情况:这个函数的函数名写错了(C语言对大小写敏感)
  • 多文件跨文件去编译链接的时候,我们一直使用的VS是集成性开发环境,不需要我们动手去编译链接。但是我们在Linx环境底下,gcc上面就需要动手用指令去执行这个步骤。


程序的执行环境

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。
  2. 在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。(没有操作系统的环境下,单片机,烧板子了解一下)。
  3. 程序的执行便开始。接着便调用main函数。
  4. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。内存空间的管理都是由函数栈帧的创建与销毁来创建和管理的(函数栈帧的创建与销毁---前面博文讲过复习一下哦)
  5. 终止程序。正常终止main函数;也有可能是意外终止。

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!我想说:最近有很多小伙伴和我交流说学不懂,告诉大家,一定要有耐性哦,乖乖敲代码,留给自己足够的时间与耐性。慢慢悟,学着学着自然就会了

希望大家都有好好学习,好好敲代码。好好生活哦

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com

联系------→【邮箱:2784139418@qq.com】

目录
相关文章
|
2月前
|
存储 自然语言处理 编译器
【C语言】编译与链接:深入理解程序构建过程
【C语言】编译与链接:深入理解程序构建过程
|
4月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
106 1
|
4月前
|
NoSQL 编译器 程序员
【C语言】揭秘GCC:从平凡到卓越的编译艺术,一场代码与效率的激情碰撞,探索那些不为人知的秘密武器,让你的程序瞬间提速百倍!
【8月更文挑战第20天】GCC,GNU Compiler Collection,是GNU项目中的开源编译器集合,支持C、C++等多种语言。作为C语言程序员的重要工具,GCC具备跨平台性、高度可配置性及丰富的优化选项等特点。通过简单示例,如编译“Hello, GCC!”程序 (`gcc -o hello hello.c`),展示了GCC的基础用法及不同优化级别(`-O0`, `-O1`, `-O3`)对性能的影响。GCC还支持生成调试信息(`-g`),便于使用GDB等工具进行调试。尽管有如Microsoft Visual C++、Clang等竞品,GCC仍因其灵活性和强大的功能被广泛采用。
133 1
|
4月前
|
编译器 C语言 计算机视觉
C语言实现的图像处理程序
C语言实现的图像处理程序
166 0
|
7月前
|
存储 C语言
简单c语言程序举例
简单c语言程序举例
83 1
|
2月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
3月前
|
存储 编译器 程序员
C语言程序的基本结构
C语言程序的基本结构包括:1)预处理指令,如 `#include` 和 `#define`;2)主函数 `main()`,程序从这里开始执行;3)函数声明与定义,执行特定任务的代码块;4)变量声明与初始化,用于存储数据;5)语句和表达式,构成程序基本执行单位;6)注释,解释代码功能。示例代码展示了这些组成部分的应用。
100 10
|
4月前
|
自然语言处理 编译器 C语言
C语言程序的编译
C语言程序的编译
69 2
|
5月前
|
前端开发 C语言 C++
C语言03----第一个程序HelloWorld(vs版)
C语言03----第一个程序HelloWorld(vs版)
|
6月前
|
C语言 图形学 C++