一. 编译与链接
1.组成程序的每个源文件通过编译过程分别转换成目标代码。
2.每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序 。
3.链接器同时也会引入标准C函数库中任何被该程序用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。(隔离编译,一起链接)
4.编译过程分为预处理、编译、汇编
预处理:gcc -E 文本操作(#include,#define,删除注释行)
编译:gcc -S 把C语言转化成汇编代码(语法、词法、语义分析,符号汇总)
汇编:gcc -c 把汇编程序转化成机器指令,形成符号表(生成 .o 文件)
链接:合并段表,合并符号表并重定位
5.程序执行的过程
a. 程序载入内存;
b. 程序执行开始。接着调用main函数;
c. 开始执行程序代码。这时程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。程序同时可以使用静态内存,存储在静态内存中的变量在程序的整个执行过程中一直保留他们的值;
d.终止程序。正常终止main函数,也有可能是意外终止。
二. 预处理
-
1.预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C, 其值为1,否则未定义
这些预定义符号都是内置的。
#define
(1) #define定义标识符
#define NUM 500
(2)#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现称为宏或定义宏。
#define SQUARE(x) x*x
对数值表达式进行求值的宏定义都应该加括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
(3) #define 替换
在程序中扩展#define定义符号和宏时,要注意:
在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
替换文本随后被插入到程序原来文本的位置,对于宏,参数名被他们的值替换。
最后,再次对结果文件进行扫描,看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。
a. #和##
#define PRINT(FORMAT, VALUE) printf("the value of " VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);
上面的代码运行错误。只有当字符串作为宏参数时才可以把字符串放到字符串中。
而# 将参数变成字符串。
#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
#include<stdio.h>
#define CAT(X, Y) X##Y
int main()
{
int value36 = 40;
printf("%d\n", CAT(value, 36));
return 0;
}
//输出:40
3.宏和函数
宏的优点:
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之,宏可以适应于整形、长整形、浮点型等可以用来比较的类型。宏时类型无关的。
宏的缺点:
每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
宏没办法调试。
宏由于类型无关,不够严谨。
宏可能带来运算符优先级的问题,容易出错。
宏的参数可以出现类型,但是函数做不到。
(1)带副作用的宏参数
x+1; //不带副作用
x++; //带副作用
#define MAX(a,b) ((a)>(b)?(a):(b))
......
x = 5;
y = 6;
z = MAX(x++, y++);
命名约定:
宏名全部大写,函数名不全大写。
(2)#undef:用于移除一个宏定义。