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

33.0、C语言——C语言预处理(1) - 翻译环境详解

33.0、C语言——C语言预处理(1) - 翻译环境详解

程序的 翻译环境 和 执行环境

在ANSI C的任何一种实现中,存在两个不同的环境;

第 1 种是翻译环境,在这个环境中源代码被转换为可执行的机器指令;

第 2 种是执行环境,它用于实际执行代码;

详解:翻译环境 = 编译 + 链接

        在我们的项目中 可能会出现多个 源文件,那么每一个源文件都会被系统单独的当成一个单元去单独处理;

        源文件在经过 编译器 处理之后 会产生一个目标文件也就是 .obj 文件,然后等待每一个目标文件都通过 链接器链接库 处理之后,会将所有目标文件链接到一起,然后产生一个可执行程序 .exe;

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

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

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

那么其实编译又可以细分为三个过程 ->

        1. 预编译

        2. 编译

        3. 汇编

详细介绍:

其实这三个过程在我们的 vs编译器 中不太好体现出来,还是用 Linux 比较容易理解;

预处理阶段 ->    

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

        那么在进行预编译处理的时候,相当于把 #include <xxx.h> 执行了,将所有的头文件包含的内容添加到了我们的 源文件 中; 

        其实不仅仅只是将头文件包含进了源文件,还做了一些其他的事情,比如说 对源文件的注释删除(使用空格去替换注释),因为注释的信息对编译器来说没有任何意义;

        还有比如说 #define 定义的一些常量会在预编译阶段,将这些常量全部替换成 值 ,比如说 #define max 100 那么在预处理阶段会将程序中 所有的 max 换成 100;

        其实总的来说,就是一些文本的操作,文本的包含文本的替换啥的;

编译阶段 ->

        2. 编译选项 gcc -S test.c 编译完成之后就停下来,结果保存在 test.s 中;

        将我们的 C代码 翻译成了 汇编代码,那么详细的的来说就是做了以下一些动作 ->

        1. 语法分析

        2. 词法分析 ( 涉及到编译原理,这里不细说 )

        3. 语义分析

        4. 符号汇总 (比如说一些 函数名、全局变量等都汇总起来)

汇编阶段 ->

        3. 汇编 gcc -c test.c 汇编完成之后就停下来,结果保存在test.o 中;

        将之前编译好的汇编代码,转换成 object 目标文件【  当然这个目标文件我们依然看不懂,因为他是将汇编代码转换成了二进制指令 / 代码 】;然后所有的目标文件通过链接器生产可执行程序; 

        当然汇编阶段还做了一件事情 -> 形成符号表 ,符号就是之前在编译阶段汇总的符号,那么在汇编阶段会将符号以及符号对应的地址存储到符号表中;

例如像以下代码 ->

这里要汇总的符号有两个 一个是 main 一个是 add;

        在该源文件中我们确切的知道 main() 函数式存在的,所以可以有一个相对应的地址存到符号表中;但是 add() 函数只是声明,并不知道他是否确切存在,所以会存入一个无意义的地址;

链接阶段 ->

链接器要做以下两件事请 ->
1. 合并段表;

2. 符号表的合并和重定向;

1. 那么先来说一下什么是:合并段表 ->

        首先每个目标文件中会分为不同的段,每段会存放一些程序的数据、代码等;不过每个目标文件的段结构、格式是一样的【这种格式被称作 elf 文件格式,感兴趣的可以百度一下】,只是每段存放的东西不一样;

        经过链接阶段时,首先会把每个目标文件中相对应的段位置里的数据合并到一起,最终形成一个目标文件;

2. 什么是符号表的合并和重定向 ->

        在经过编译阶段后,会形成 符号表,符号表中存放了符号以及他们的地址;那么重复的符号会合并,合并之后如果他们的地址不同则取 有效地址 存入到合并后的符号表中;

符号表的作用是啥呢?

        比如说符号表里现在存放着一个函数 Add() 的 符号 Add 以及他的地址,但是这个函数根本就没有定义,只是声明了一下;那当我们在程序中调用这个 Add() 函数的时候,就会去符号表中查找有没有这个符号 Add,查找后发现确实有然后根据他存放的地址去调用Add() 函数,但是发现这个地址是一个无效地址,根本找不到那么就导致函数调用失败了;

        当我们的 编译阶段 通过 且 链接阶段 也通过后那么可执行程序就 成功生成了【其实可执行程序也是 elf 文件格式】,那么到这里我们的翻译环境就基本结束了; 

相关文章:

  • java-php-python-springboot网上订餐系统计算机毕业设计
  • 【VUE项目实战】66、上线-通过node创建Web服务器
  • About 9.25 This Week
  • 三、基本命令
  • MySQL中select ... for update会锁表还是锁行?
  • 计算机毕业设计选题 SSM大学生企业推荐系统(含源码+论文)
  • 【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
  • js逆向-逆向基础
  • 【前端】【探究】HTML - input类型为file时如何实现自定义文本以更好的美化
  • 二叉树的dp问题和Morris遍历
  • 重新认识IO以及五种IO模型(理论认识)
  • leetcode: 647. 回文子串
  • SQL语言概述与SQL语言的数据定义
  • NIO知识总结三
  • c语言分层理解(动态内存分配)
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • 11111111
  • Cookie 在前端中的实践
  • GraphQL学习过程应该是这样的
  • Laravel 中的一个后期静态绑定
  • MySQL的数据类型
  • Solarized Scheme
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • vue2.0项目引入element-ui
  • 当SetTimeout遇到了字符串
  • ------- 计算机网络基础
  • 前端面试题总结
  • 区块链将重新定义世界
  • 使用API自动生成工具优化前端工作流
  • MPAndroidChart 教程:Y轴 YAxis
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • 湖北分布式智能数据采集方法有哪些?
  • 选择阿里云数据库HBase版十大理由
  • ​iOS安全加固方法及实现
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • (07)Hive——窗口函数详解
  • (1)(1.13) SiK无线电高级配置(五)
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (二)fiber的基本认识
  • (六)软件测试分工
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (三)mysql_MYSQL(三)
  • (十八)SpringBoot之发送QQ邮件
  • (四)Controller接口控制器详解(三)
  • (转)母版页和相对路径
  • .NET CF命令行调试器MDbg入门(一)
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .net 设置默认首页
  • .NET 中让 Task 支持带超时的异步等待
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .NET6实现破解Modbus poll点表配置文件
  • .Net小白的大学四年,内含面经
  • .NET中两种OCR方式对比