最开始了解计算机的大家,应该都听过计算机最开始是由很多个10,也就是通电表示1,不通电表示0这样的电子元件构成。那么演变到如今,我们平时写的代码,不管是哪一种语言,都是我们可以很清楚识别的代码,但是这些我们很轻松可以理解的代码,如果原封不动的交给计算机,那肯定是不可以的,所以就需要把我们写的代码通过一系列的操作转换成计算机可以识别的10指令。
以C语言为例,我们写的 test.c文件,最终会通过一系列的转换变成 test.exe 文件,那么这个中间就要经历翻译环境最终到运行环境。
翻译环境中又可以分为编译和链接
编译
编译过程又可以分为三个部分,首先进行预处理阶段,生成预处理指令,处理后生成 .i 文件。接下来进行编译,这一步进行语法分析、词法分析、语义分析、符号汇总,生成 .s 文件。再接着就是汇编,形成符号表,这一步就有汇编指令转换成二进制指令,生成 .o 文件。
链接
链接阶段,主要是进行合并段表,符号表的合并和符号表的重定位,由此生成 .exe 可执行文件。
程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
预处理
预定义的符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include <stdio.h>
int main()
{
printf("file:%s line:%d\n", __FILE__, __LINE__);
return 0;
}
代码执行的结果可以看到预定义符号的作用
在预处理阶段,还有一个十分重要的定义那就是 #define
#define 的定义有两种,一种就是定义标识符,另一种就是定义宏
注意:define 无论是哪一种定义,define 定义都是直接替换,不做任何改变
#define 定义标识符的语法:
#define name stuff
#define MAX 1000
int main()
{
printf("%d", MAX);
return 0;
}
#define 定义宏:
这里需要注意的就是,定义宏要严格的把每个参数和运算用括号限制起来,否则就会出现问题
举例说明一下:
#define MAX(x,y) x + y
int main()
{
int a = 10;
int b = 12;
printf("%d", 10 * MAX(a, b));
return 0;
}
#define MAX(x,y) ((x)+(y))
int main()
{
int a = 10;
int b = 12;
printf("%d", 10 * MAX(a, b));
return 0;
}
#define 替换规则 在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先 被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。
注意: 1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。 2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
宏和函数对比
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比 函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方: 1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。 2. 宏是没法调试的。 3. 宏由于类型无关,也就不够严谨。 4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#undef 这个的主要作用就是移除一个宏定义
条件编译
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
有了这些条件编译,在预处理阶段就可以更好的识别代码中一些问题的存在,例如多个文件互相嵌套调用时,避免重复生成头文件的内容。