1. 程序的翻译环境和执行环境
翻译环境:在这个环境中源代码被转换为可执行的机器指令。
执行环境:它用于实际执行代码。
2. 详解编译+链接
首先我们看一张图:
在一个工程中,我们需要协作,那必须创建多个源文件(test.c),那么每个源文件经过编译器编译变为test.obj,再由链接器加链接库把test.obj文件变为test.exe可执行文件。
接下来,我们具体看过程:
其实笼统地来说,由.c文件到.exe文件,只有两步,编译和链接,但是细分的话,有四步:
所以仔细看图以后,我们可以知道,其实到可执行文件有四步,预处理,编译,汇编,链接。
编译过程中符号汇总,汇编产生的符号表,以及后面链接时要进行的符号表的合并和重定位。
都是非常重要的。
3.运行环境
程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用 main 函数。( 程序的入口)
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static )内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止 main 函数;也有可能是意外终止。
4.预处理
1 预定义符号
__FILE__ // 进行编译的源文件
__LINE__ // 文件当前的行号
__DATE__ // 文件被编译的日期
__TIME__ // 文件被编译的时间
__STDC__ // 如果编译器遵循 ANSI C ,其值为 1 ,否则未定义
printf ( "file:%s line:%d\n" , __FILE__ , __LINE__ );可以通过打印字符串的方式来显示。
2.#define 定义标识符
#define +标识符+符号内容
(注意的一点是,#define定义标识符最后尽量不要加;(分号),因为会将分号当作标识符内容一起替换掉。)
比如:
#define MAX 1000; #define MAX 1000 int main() { int a=MAX; return 0; }
为了防止分号多出现这种情况,尽量不要加;。
3.续行符
如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ ,\ __DATE__,__TIME__ )
其实相当于\转义了回车,当内容太长的时候,就可以利用续行符来解决,其实是在同一行。
4.#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
举例说明:
#define square(x) ((x)*(x)) int main() { int a=5; square(a+1); } 会换成: ((5+1)*(5+1))
在写宏时,不要吝啬括号,能加括号的地方就加括号,会防止很多运算符优先级打乱预期的结果。
#define square(x) x*x int main() { int a=2; Add(a+1); } 会换成: 2+1*2+1
从而没有到达我们平方的预期。
5.宏替换规则
在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤:
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。 但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。