程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。
翻译环境
翻译环境一般需要两个工作量就是编译和链接。
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接到程序中。
编译
编译对于vs2022来说,是由cl.exe完成的。编译又有三个阶段,很难给大家演示出来,便只说出来了解一下即可。分为,预编译(预处理)、编译、汇编。
预处理会把test.c文件生成一个test.i文件完成了主要三个功能:
1.注释的替换,替换成大空格,在test.i文件中不会显示注释。
2.头文件的包含。把头文件变成具体的代码引入。
3.define的替换。
编译,会把test.i文件具体实现为汇编代码也就是一个test.s文件,做到了例如:
词法分析、语法分析、语义分析、符号分析。
最后汇编会把test.i文件实现为test.o(test.obj)文件,生成了符号表。
链接
链接主要就是链接目标文件和链接库生成可执行的二进制程序。
具体做到了:
符号表的生成、符号表的合并和重定位。
执行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
预处理详解
预定义符号
主要有:
FILE //进行编译的源文件
LINE //文件当前的行号
DATE //文件被编译的日期
TIME //文件被编译的时间
STDC //如果编译器遵循ANSI C,其值为1,否则未定义
用代码实现来看一下具体作用:
#include<stdio.h> int main() { printf("进行编译的源文件:%s\n", __FILE__); printf("文件当前的行号:%d\n", __LINE__); printf("文件被编译的日期:%s\n", __DATE__); printf("文件被编译的时间:%s\n", __TIME__); return 0; }
可以看一下和我电脑右下方的时间是一样的。
#define
这个定义标识符我们经常用到,那么提一个问题在使用完要不要加一个分号呢?
答案是不。我们来看一下为什么:
#define MAX 1000; #define MAX 1000
建议不要加上 ; ,这样容易导致问题。
比如下面的场景:
if(condition) max = MAX; else max = 0;
这里会出现语法错误。如果使用的是第一个那么会有两个分号,第二个则是没有语法问题。
#define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
宏在使用的时候最好每个参数都加一个(),至于为什么我们直接上代码看原因。
#define SQE(x) x*x int main() { int a = 5; printf("a+1的平方是:%d", SQE(a + 1)); return 0; }
这样发生的原因就是因为宏只会替换过去,这个时候而因为符号优先级可能会导致意外的错误。
为了尽量避免这样的问题发生,我们可以把每个参数带上()。
#define SQE(x) (x)*(x) int main() { int a = 5; printf("a+1的平方是:%d", SQE(a + 1)); return 0; }
这样可以一定程度的避免因为符号优先级而导致的错误。
提示:
用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中 的操作符或邻近操作符之间不可预料的相互作用。