翻译环境,在这个环境中源代码被转换为可执行的机器指令。
执行环境,它用于实际执行代码。
翻译环境
翻译环境分为编译与链接,是将我们编译的代码转换成程序运行的二进制代码进行转换。下面是讲述翻译环境:
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(.obj)。
- 每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序(.exe)。
- 【链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库】链接库,将其需要的函数也链接到程序中。
编译
编译分为预编译(预处理)、编译、汇编,下面讲这些程序的作用:
预编译(预处理)
预编译是将头文件包含、宏定义的替换和删除、 注释删除等文本操作,是将.c文件进行预处理后变成.i文件。
编译
将C语言代码经过语法分析、词法分析、语义分析、符号汇总翻译成汇编代码,生成.s文件。
汇编
将汇编代码翻译成二进制代码,在VS下生成的是.obj文件,在vscode-gcc环境下生成的是.o文件(目标文件)。
链接
所有的文件合并成一个可执行文件(.exe)。
运行环境
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
------------预处理
1.预定义符号
2.#define
2.1#define定义标识符 (所有的定义都不需要加;)
#define MAX 1000【将函数中的MAX替换成1000】
#define reg register 【为 register这个关键字,创建一个简短的名字】
#define do_forever for(;;) 【将do_forever替换成for(;;)死循环】
#define CASE break;case 【将CASE替换成break;case,所以第一个case还是小写】
2.2#define定义宏
#define SQUARE( x ) x * x 【SQUARE( 5 ); --> 5*5;】
【int a = 5; printf("%d\n" ,SQUARE( a + 1) );--> a+1*a+1;】
所以定义宏时不要吝啬括号
2.3#define替换规则
2.4#和## (只能在宏中定义)
int i = 10;
#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE "is "FORMAT "\n", VALUE)
【PRINT("%d", i+3);--> the value of i+3 is 13\n
代码中的 #VALUE 会预处理器处理为:"VALUE"】
#define SUM(x, y) x##y
【 SUM(L, X) = 100;--> printf("%d\n", SUM(L, X));】
2.5带副作用的宏参数
z = (x++) > (y++) ? (x++) : (y++); //首先需要判断两个数的大小所以需要先使用值再++,得到x=6,y=9.然后就是执行后面的语句为假y++ ,同样也是先使用值,所以z=9,原本y的值是9,再++则y=10,x不变x=6。
宏和函数对比
- 宏比函数在程序的规模和速度方面更胜一筹;
- 函数需要定义类型,宏不需要;
- 使用宏会让原本长的代码加长;
- 函数可以,宏不能调试;
- 宏可能带来运算符优先级的问题,所以要括号;
- 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不一样的结果;
- 宏不能递归,函数可以;
2.6命名约定:一般宏全大写,函数首字母大写或驼峰表示法;
3.#undef
4.命令行定义
某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大,我们需要一个数组能够大一些。
5.条件编译
常见的条件编译指令
(1.
(2.多分支条件编译
(3. 是否被定义
(4.嵌套指令
5.文件包含
库文件是放在标准库里面的, 虽然库文件也可以使用“”进行包含, 但是这样效率就会变低,而且这样不容易区分本地文件和库文件了。
- #pragma once //当一个项目中文件数过多, 可能出现头文件重复包含的情况。
- 或者在头文件中这样写,也是只包含一次。