[深入浅出C语言]浅析编译与预处理(篇一)

149 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

前言

        本文就来分享一波作者对编译和预处理的学习心得与见解。本篇属于第一篇,主要介绍程序的翻译环境和运行环境的一些内容,属于前置铺垫,后续还有,可以期待一下。

        笔者水平有限,难免存在纰漏,欢迎指正交流。

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

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

        第一种是翻译环境,将源代码转换成可执行的机器指令。

        第二种是执行环境,用于实际执行代码。

翻译环境

        对于翻译环境,主要有编译和链接两个步骤。

image.png

         源文件编译生成目标文件,目标文件再经由链接器与链接库链接最终生成可执行程序。

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

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

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

         实际上,编译过程可分为三个过程:预编译、编译和汇编。

image.png

        对于编译过程中的符号汇总,举个例子,下面是同一工程下的两个源文件,那么在编译过程中,所谓汇总的符号,对add.c来说就是Add,对test.c来说,就是Add和main。实际上汇总的符号是文件中的全局符号(全局变量、函数名)。

image.png

         而后在汇编过程中,汇总的符号会生成elf格式的符号表,是分段的,装着符号和其对应地址,当进入链接过程时,符号表合并,相同符号会合并剩下一个有效符号。比如说上面的例子中,test.c文件中的Add符号不是有效符号,其地址无实际意义,因为该函数的定义是在Add.c文件而非test.c文件,test.c中仅有其声明罢了,实际上在链接时两个Add符号合并后保留Add.c文件中的Add的地址。

image.png

例题

        由多个源文件组成的C程序,经过编辑、预处理、编译、链接等阶段会生成最终的可执行程序。哪个阶段可以发现被调用的函数未定义?

        答:链接阶段。

分析

        预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体,所以是链接时出错的。

这里附上每个步骤的具体操作方式:

预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。

编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。

链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。

        需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。

运行环境

        在运行环境中进行程序的执行,其过程: 

        1. 程序必须载入内存中。

        在有操作系统的环境中:一般这个由操作系统完成。

        在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

        2. 程序的执行便开始。接着便调用main函数。

        3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

        4. 终止程序。正常终止main函数;也有可能是意外终止。


以上就是本文全部内容了,感谢观看,你的支持就是对我最大的鼓励~

v2-1a2bf23551e8c0438c155f93aab4b495_720w.jpg