一、程序的编译
我们写的源文件(*.c)是经过怎样的处理生产可执行文件(*.exe)的呢?这种处理有两个步骤—编译和链接。源文件在编译阶段通过编译器将每个源文件转换为目标文件(这些文件是可执行的机器指令),再通过链接器将其捆绑到一起,生成一个完整的可执行程序。
1、 编译阶段
编译阶段可细分为3个阶段:预处理(即预编译)、编译、汇编
预处理:主要进行头文件的包含、处理预处理指令(如:#define定义符号的替换)、删除注释。此时(*.c)文件转换为(*.i)文件。
编译:主要进行语法分析、词法分析、语义分析、符号汇总,此阶段会将c语言代码转换为汇编代码。此时(*.i)文件转换为(*.s)文件。
汇编:此阶段会形成符号表,将汇编代码转换为二进制指令(机器指令)。此时(*.s)文件转换为(*.o)文件。
2、链接
此阶段主要完成合并段表、符号表的合并和重定位工作,将(*.o)文件链接到一起生成可执行文件。
二、预处理详解
1、预定义符号
__FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1;如果不遵循,则编译器不认识这个符号,是个未定义标识符,此时使用这个符号程序会报错。该符号的作用是判断编译器是否遵循ANSI C。
下面为大家展示这些符号的作用。
执行结果为:
2、#define定义的标识符
语法:
#define MAX 100
这句代码的意思是数字“100”替换成符号MAX,例如:
#include <stdio.h> #define MAX 100 int main() { int i = 1 + MAX; printf("%d", i); return 0; }
结果:
注意:define定义的标识符只进行替换,并不会在替换过程中执行运算,例如
#include <stdio.h> #define MAX 5+5 int main() { int i = 10; int x = i * MAX; printf("%d", x); return 0; }
这个程序的结果是多少呢?100吗?答案其实是55,因为define只是简单的替换,i*MAX其实本质是i*5+5,即10*5+5=55.
3、#define定义的宏
语法:
#define SQUARE(x) x * x
当我们在程序中写下:SQUARE(5)这句代码,这预处理时,这句代码就会被替换成5*5。例如:
#include <stdio.h> #define SQUARE(x) (x) * (x) int main() { int i = SQUARE(5); printf("%d", i); return 0; }
程序结果为:
注意:同样的#define定义的宏也只是进行简单的替换。
例如:
#include <stdio.h> #define SQUARE(x) x * x int main() { int a = 5; int i = SQUARE(a+1); printf("%d", i); return 0; }
这个程序的结果是什么呢?答案是11,SQUARE(a+1)等同于SQUARE(5+1),即5+1*5+1=11,而不是6*6=36。
宏常常被用于执行简单的运算,比如在两个数中找出较大的一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
那么为什么不用函数呢,因为宏有两个优势:
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
但宏也有劣势:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。(就是上面我提醒大家需要注意的地方)
4、#unef
这条指令用于移除一个#define定义标识符或宏定义。
例如:
#define MAX 100 int main() { int m = MAX; #undef MAX int n = MAX;//err return 0; }
这个程序会报错,因为那个int n=MAX中的MAX的定义被移除了,而其他的不会(m仍然是正确的)。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注好代码网的更多内容!