一.程序的翻译环境和执行环境
概念介绍:
在ANSI C的任何一种实现中,都会存在两个环境。
1.翻译环境:将源代码转换成可执行的机器指令。(编译器)
2.执行环境: 实际执行代码。(操作系统)
二.详解翻译环境
即:一个源程序文件怎么转化成一个可执行程序的。
流程:多个源文件单独经过编译器处理,再经过链接器链接,最后形成一个可执行程序。
1.编译
(1)预编译(文本操作)
文本操作:文件的替换调用
a.头文件的包含
#include — 预处理指令
b.#define定义符号的替换
#define —预处理指令
c.删除注释
(2)编译(把C语言代码翻译成汇编代码)
a.语法分析
b.词法分析
c.语义分析
d.符号汇总:只汇总全局符号 例如:Add main(链接时会用到)
(3)汇编(汇编指令翻译成二进制指令)
a.形成符号表:每个源文件都是单独编译,都形成自己的符号表(包括引用的外部符号),并带有符号地址,如果是外部符号就赋一个无意义的地址
(4)链接(把多个经过编译的源文件+链接库 链接)
a.合并段表:把不同源文件的相同段进行合并
b.符号表的合并和重定位:把不同源文件形成的符号表合并,使得外部声明的符号得到其真正的地址(所以如果未定义,会在链接的时候报错:无法解析的外部符号)
三.概述运行环境
程序执行的过程
1.程序必须载入内存中。在有操作系统的环境中,载入由操作系统完成。在独立的环境中,载入到内存必须由手工安排。
2.程序的执行开始。调用main函数。
3.开始执行代码。这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。同时也就可以使用静态内存。
4.终止程序。正常终止main,也可能是异常终止。
四.预处理详解
1.预定义符号
a.__FILE__ 进行编译的源文件
b.__LINE__ 文件当前的行号
c.__DATE__ 文件被编译的日期
d.__TIME__ 文件被编译的时间
e.__STDC__ 如果编译器遵循ANSI C ,其值为1,否则未定义
2.#define
(1)#define定义标识符
非常灵活,在末尾不要加;会出问题
因为宏定义是直接替换的,所以由于运算符优先级的不同,会导致一些问题。
(2)#define定义的宏
所以我们在进行宏定义的时候,要把变量都加上括号、把宏的整体也加上括号。
注意:宏参数和#define定义中可以出现其他#define定义的符号。但是宏不能递归。
字符串中出现宏定义符号 不会被替换
#undef NAME 这条指令用于移除一个宏定义
3.#和##
(1)# 将参数插入到字符串中
铺垫:由于C语言对于字符串的打印有一个会合并的特点。(两个打印出来效果一样)
所以
我们用宏可以实现如下效果:
演示1:
#include<stdio.h> #define PRINT(N) printf("the value of "#N" is %d\n",N) //无分号! int main() { int a = 10; PRINT(a); int b = 20; PRINT(b); }
演示2:
#include<stdio.h> #define PRINT(N,format) printf("the value of "#N" is "#format"\n",N) //无分号! int main() { int a = 10; PRINT(a,%d); double b = 3.14; PRINT(b,%lf); }
(2)## 可以把它两边的符号合成一个符号
其允许从分离的片段中创建标识符
演示:
#include<stdio.h> #define CAT(name,num) name##num int main() { int class105 = 105; printf("%d\n", CAT(class, 105)); }
4.带有副作用的宏参数
b=a+1; 不带副作用
b=a++; 带有副作用 改变了a的值 宏替换是自左向右一步一步替换的,此时a的值已经发生改变
演示:
#include<stdio.h> #define MAX(x,y) ((x)>(y)?(x):(y)) int main() { int a = 5; int b = 8; int c = MAX(a++, b++); printf("%d %d %d", a, b, c); }
5.宏和函数的对比
6.命令行定义(在编译的过程中定义符号)
当我们根据同一个源文件编译出不同版本可用命令行定义,例如:可以用来调整数组大小。
7.条件编译
(1)#if
#if 常量表达式 如果为真 进入编译
......
#endif 结束编译
(2)多个分支的条件编译
#if 常量表达式
...
#elif 常量表达式
...
#endif
(3) 判断是否被定义
a.如果定义了 就为真 参与编译
#if defined(NAME)
...
#endif
#ifdef NAME
...
#endif
b.如果没定义 参与编译
#if !defined(NAME)
...
#endif
#ifndef NAME
...
#endif
(4)嵌套指令
与if语句嵌套相同,不过每组条件编译语句都要用#endif来结束
8.文件包含
(1)头文件被包含的方式
a.本地文件包含
#include "filename"
查找策略:两步走:先在源文件所在目录下查找,如果未找到,就像查找库函数头文件一样在标准位置查找头文件。
b.库函数包含
#include
查找策略:直接去标准路径下查找
(2)防止重复包含
a.
#ifndef __NAME_H__ (名字随便写 它只是判断这条语句之前有没有走过)
#define __NAME_H__
.......
#endif
b.
#pragma once