这章的学习要点:
1、程序的翻译环境
2、程序的执行环境
3、详解:C语言程序的编译+链接
4、预定义符号介绍
5|预处理指令#define
6、宏和函数对比
7、预处理操作符#和##的介绍
8、命令定义
9、预处理指令#include
10、预处理指令#undef
11、条件编译
1、程序的翻译环境和执行环境
翻译环境:在这个环境中源代码被转换为可执行的机器指令
执行环境:它用于实际执行代码
2、详解编译+链接
翻译环境:
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序
员个人的程序库,将其需要的函数也链接到程序中。
编译的几个阶段:
先举一个代码的例子
sum.c
int g_val = 2016;
void print(const char *str)
{
printf("%sn", str);
}
test.c
#include
int main()
{
extern void print(char *str);
extern int g_val;
printf("%dn", g_val);
print("hello bit.n");
return 0;
}
编译分为四个阶段分别为:
预编译阶段(*.i) 编译(*.s) 汇编(生成可重定位的目标文件*.o) 链接
预处理指令 语法分析 形成符号表 1、合并段表
词法分析 汇编指令->二进制指令 2、符号表的合并和
语义分析 符号表的重定位
符号汇总
运行环境:
程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
(《程序员的自我修养》这本书可以深入学习)
3、预处理详解
预定义符号
__FILE__
__LINE__
__DATE__
__TIME__
__STDC__//进行编译的源文件
//文件当前的行号
//文件被编译的日期
//文件被编译的时间
//如果编译器遵循ANSI C,其值为1,否则未定义
#define定义宏
#define SQUARE( x ) x * x
这个宏接受一个参数x,若之后声明
SQUARE(5);
则预处理器就会用5*5替换上面的表达式
但是一定要注意符号的优先级,如果是
SQUARE(5+1);
则结果并不是36而是11,为了避免这个问题可以在定义宏时套上括号
#define SQUARE(x) ((x)*(x))
#define替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
宏与函数对比
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以
用于>来比较的类型。
宏是类型无关的
宏的缺点:当然和函数相比宏也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序
的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:
把宏名全部大写
函数名不要全部大写



