网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
大家好,我是纪宁。
在ANSI C的任何一种实现中,都存在两个不同的环境。第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。翻译环境中也有很多的内部原理,一起来学习吧。
一、程序的翻译环境和执行环境
1.程序编译过程
其中,源文件是文件名后缀为 .c 的文件,目标文件是文件后缀名为 .obj 的文件。
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中
2.编译内部原理
在预处理阶段,系统会自动处理掉所有的预编译指令。 包括删去注释,将头文件包含,#define 定义符号和宏的替换等等
而在编译阶段,系统会将C语言代码翻译成汇编指令,还有语法分析、词义分析、语义分析、符号汇总等
在汇编阶段。系统将汇编指令翻译成二进制指令,并生成.o目标文件,形成符号表。
最后在链接阶段合并段表,重定位和合成符号表(在链接阶段可发现函数是否正常定义)
3.执行环境
首先,程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
接下来就是程序的执行开始,调用main函数开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
最后就是终止程序。正常终止main函数,也有可能是意外终止。
二、程序运行前的预处理
1.预定义符号归纳
下面的这些符号都是C语言内置符号
__FILE__ | 进行编译的源文件 |
__LINE__ | 文件当前的行号 |
__DATE__ | 文件被编译的日期 |
__TIME__ | 文件被编译的时间 |
程序中使用对应的符号在预处理阶段自动转化为对应的含义,测试代码如下
#include<stdio.h>
int main() {
printf("%d\n", __LINE__);
printf("%s\n", __TIME__);
printf("%s\n", __DATE__);
printf("%s\n", __FILE__);
return 0;
}
运行结果
由于_LINE_在第四行,所以转化结果为4;时间和日期就是我测试代码的时间、日期;_FILE_则对应我源文件的路径。
2.define定义标识符
语法如下:
#define name stuff
在预处理阶段,编译器会自动将代码中的 name 全部替换为 stuff 。但需注意在写#define定义标识符的时候,后面不能加 ;,预处理阶段编译器会将 ; 也当成要替换的内容。
3.define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。它的使用方法与函数类似,只不过将函数传参改成了直接替换。因为改成了直接替换,所以宏的参数尽量加括号修饰。其次,参数列表的左括号必须与name紧邻,因为如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
宏的参数一般会被多次替换,所以不能使用那些带 ‘副作用’ 的参数,副作用即表达式求值中出现的永久性效果,如自加自减这些运算。
例如,求一下如下代码的运算结果:
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);
大部分人可能会理所当然的以为传过去的是x++和y++表达式的值,那么结构就应该是z的结果剧应该是8,x的结果应该是6,y的结果应该是9 ?可实际上并非如此:
这就是参数带有副作用的影响
4.define替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
还有几点需要注意
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
5.宏和函数的对比
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!