一文带你深入浅出C语言程序环境和预处理

本文详细介绍了C语言的程序翻译环境和运行环境,包括编译和链接过程,以及预处理的各个知识点,如预定义符号、逻辑行、宏定义(不带参数、带参数的宏、宏替换规则、副作用问题)、类函数宏与函数的选择、条件编译和文件包含。文章旨在帮助读者理解C语言中的预处理原理和实践。
摘要由CSDN通过智能技术生成

目录

前言

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

(1)翻译环境

例题

分析

(2)运行环境

2.预处理详解

2.1 预定义符号 

2.2 逻辑行

2.3 #define定义宏

2.3.1 不带参数的宏

2.3.2 带参数的宏

2.3.3 #define 替换规则

2.3.4 用宏参数创建字符串:关于#和##

2.3.5 某些宏参数带有副作用

2.4 类函数宏与函数的选择问题

2.4.1 宏的优势&函数的不足

2.4.2 宏的不足&函数的优势

2.4.3 如何选择

2.4.4 宏和函数对比汇总表

2.5 #undef  

2.6 命令行定义

2.7 条件编译

2.7.1 #if

2.7.2 多个分支的条件编译

2.7.3 判断是否被定义

2.7.4 嵌套指令

2.8 文件包含 #include

🔷敬请期待更好的作品吧~


前言

        本文分享一波C语言中有关程序环境和预处理相关的知识,由于笔者水平有限,难免存在纰漏,读者各取所需即可。

给你点赞,加油加油!

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

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

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

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

(1)翻译环境

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

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

        组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
        每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
        链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

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

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

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

例题

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

        答:链接阶段。

分析

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

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

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

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

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

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

(2)运行环境

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

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

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

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


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


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


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

2.预处理详解

2.1 预定义符号 

__FILE__
__LINE__
__DATE__
__TIME__
__STDC__
//进行编译的源文件
//文件当前的行号
//文件被编译的日期
//文件被编译的时间
//如果编译器遵循ANSI C,其值为1,否则未定义

        这些预定义符号都是语言内置的。

        例如:

printf("file:%s line:%d \ndate:%s time:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);

         那这么些符号有啥用呢?可以用来记录日志。

2.2 逻辑行

        在预处理前,编译器定位到每一个续行符即反斜杠 \ ,删除它并且将几个物理行合成一个逻辑行。如:

printf("That's \ 
right.");            //两个物理行

printf("That's right");  //转换成一个逻辑行

        编译器用一个空格字符代替每一条注释,所以实际上注释压根就没进入到编译过程,更不用说最后的可执行程序了,因为注释是给人看的,机器并不会去看。

        预处理器指令从#开始,执行长度为一个逻辑行,所以预处理指令若要使用多个物理行的话需要用续行符合成一个逻辑行。

2.3 #define定义宏

        每行#define由三部分组成,用宏代表值的称为类对象宏,代表函数的称为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桦秋静

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值