一、程序的翻译环境和执行环境
在ANSIC的任何一种实现中,存在两个不同的环境:
- 翻译环境,在这个环境中源代码被转换为可执行的机器指令。
- 执行环境,他用于实际执行代码。
二、详解编译+链接
2.1 翻译环境
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员的程序库,将其需要的函数也链接到程序中。
2.2 编译本身也分为几个阶段:
例:
sum.c
int g_val = 2016;
void print(const char *str)
{
printf("%s\n", str);
}
test.c
#include <stdio.h>
int main()
{
extern void print(char *str);
extern int g_val;
printf("%d\n", g_val);
print("hello bit.\n");
return 0;
}
VIM学习资料
简明VIM练级攻略:
https://coolshell.cn/articles/5426.html
给程序员的VIM速查卡
https://coolshell.cn/articles/5479.html
2.3 运行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始,接着调用main函数。
- 开始执行代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
三、预处理详解
3.1 预定义符号
这些预定义符号都是语言内置的:
printf("file:%s line:%d\n", _FILE_, _LINE_);
3.2 #define
3.2.1 #define定义标识符
#define name stuff
注:在#define 定义标识符的时候,不能在最后加 ;,这里会出现语法错误,句子中会出现两个;;。
3.2.2 #define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
例:
#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
注意:
参数列表的左括号必须预name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
注:在宏定义时不要吝啬使用(),缺少()会导致产生歧义。
#define DOUBLE(x) (x) + (x)
#include<stdio.h
int main()
{
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));
return 0;
}
这里结果看似打印100,实际上打印的是55。
因为替换后为:
printf ("%d\n",10 * (5) + (5));
3.2.3 #define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
3.2.4 宏和函数的对比
宏通常被应用于执行简单的运算。
例:找出两个数中较大的一个
#define MAX(a, b) ((a)>(b)?(a):(b))
不使用函数来完成这个任务的原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的
宏的缺点:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错
宏有时候可以做到函数做不到的事情。
比如:宏的参数可以出现类型,但函数做不到。
#define MALLOC(num, type)\
(type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));