1.程序的翻译环境和执行环境
翻译环境:在翻译环境中,源文件被翻译为可执行的机器指令。 执行环境:实际执行代码的环境
2.编译与链接
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其函数一起链接到程序中。 图解:
2.1编译的过程
预编译------>编译---------->汇编-------->生成可执行文件 比如:
int add(int x,int y)
{
return x+y;
}
extern int add(int x,int y);
int main()
{
int a=10;
int b=20;
int c=add(a,b);
printf("%d",c);
return 0;
}
1.预编译(预处理最后生成.i文件)
(1).展开包含的头文件的内容#include (2).删除代码中的注释 (3).替换代码中的宏定义
2.编译-----生成.s文件
**把编程语言代码转换为汇编代码。**这过程包括语法分析,词法分析,语义分析,符合汇总。 把编程语言中的每一个关键字,函数名按照语法树的内容展开。 -----看书《程序员的自我修养》
3.汇编--------生成.o目标文件(在linux环境下目标文件后缀为.o,在windows下目标文件为.obj)
汇编过程把汇编代码转换为二进制指令,并形成符号表。
2.2链接的过程
1.合并段表 将相同的段合并为一个 2.符号表的合并和重定位 把默认的符号的地址定位为可知的地址。 链接期间多个目标文件进行链接的时候,会通过符号表查看来自外部的符号是否真实存在。
2.3关于运行环境
1.程序的运行必须要载入内存中,在有操作系统的环境下,该过程由操作系统完成。没有操作系统的环境中,需要人手工载入,比如嵌入式,单片机。 2.程序执行开始,接着调用main函数。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。 3.开始执行的程序代码,需要使用一个运行的堆栈。
3.预处理详解
3.1预定义符号
__FILE__
__LINE__
__DATE__
__TIME__
__STDC__
例:记录日志
int main()
{
FILE* pf=fopen("long.txt","w");
if(pf==NULL)
return 1;
for(int i=0;i<10;i++)
{
fprintf("%s %s %s %d %d\n",_FILE_,_DATE_,_TIME_,_LINE_,i);
}
fclose(pf);
pf=NULL;
return 0;
}
3.2#define定义标识符
#define name stuff;
#define reg register
#define do_forever for(;;)
#define CASE break;case
3.2.1 #define 定义宏
#define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
#define MAX(x,y) (x)>(y)?(x):(y)
int main()
{
int a=10;
int b=20;
int m=MAX(a,b);
printf("%d\n",m);
return 0;
}
int main()
{
int a=10;
int b=20;
int m=a>b?a:b;
printf("%d\n",m);
return 0;
}
提示:用于对数值表达式进行求值的宏定义都应该加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。 例:
#define DOUBLE(x) x+x
int main()
{
printf("%d\n",DOUBLE(3))
}
#define DOUBLE(x) x+x
int main()
{
printf("%d\n",10*DOUBLE(3))
}
3.2.2 #define 替换规则
3.2.3 #和##
1.使用 # ,把一个宏参数变成对应的字符串。
#define PRINT(n) printf("the value of"#n"is %d\n",n)
int main()
{
int a=10;
PRINT(a);
int b=20;
PRINT(b);
return 0;
}
2.## 的作用: ##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
#define CAT(helloworld,num) helloworld##num
int main()
{
int helloworld1=100;
printf("%d\n",CAT(helloworld,1));
return 0;
}
3.2.4带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果.
x+1;
x++;
例:
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);
return 0;
}
4.宏和函数的对比
4.1为什么不使用函数?
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
主要是函数栈帧的调用,逻辑运算,函数返回 - 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
4.2宏对于函数的劣势
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。没有运算检查
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
...
MALLOC(10, int);
(int *)malloc(10 * sizeof(int));
|