预处理:
预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
下面总结一些常用的指令:
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#endif 结束一个#if……#else条件编译块
#error 停止编译并显示错误信息
#error 指令将使编译器显示一条错误信息,然后停止编译。
#line 指令可以改变编译器用来指出警告和错误信息的文件号和行号。
#pragma 指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁 止或允许某些烦人的警告信息。
这些都是最基本的指令,不过在预处理阶段还有一些标识符,现在就列举一下,顺便说一下其中的用途:
__FINE__ %s //进行编译的源文件
__LINE__ %d //文件所在的行号
__DATE__ %s //文件被编译的日期
__TIME__ %s //文件被编译的时间
__STDC__ %d //如果编译器遵照ACSLL C其值为1,否则未定义。
__LINE__ %d //文件所在的行号
__DATE__ %s //文件被编译的日期
__TIME__ %s //文件被编译的时间
__STDC__ %d //如果编译器遵照ACSLL C其值为1,否则未定义。
这几个标识符都是一种类型的,其作用已经标注,那么在程序中又是怎么体现呢,下面看这个程序:
#include <stdio.h>
int main()
{
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
printf("%d\n",__LINE__);
printf("%d\n",__LINE__);
};
这里我只列举了两个,如果定义的文件名为test.c,那么,最终的输出结果为:
test.c
8
9
10
#表示:对应变量字符串化
##表示:把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符
##表示:把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符
下面的程序会将上面的体现出来:
#include <stdio.h>
#define trace(x, format) printf(#x " = %" #format "\n", x)
#define trace2(i) trace(x##i, d)
int main()
{
int i=1;
char *s="three";
float x=2.0;
trace(i, d); // 相当于 printf("x = %d\n", x)
trace(x, f); // 相当于 printf("x = %d\n", x)
trace(s, s); // 相当于 printf("x = %d\n", x)
int x1=1, x2=2, x3=3;
trace2(1); // 相当于 trace(x1, d)
trace2(2); // 相当于 trace(x2, d)
trace2(3); // 相当于 trace(x3, d)
return 0;
}
宏和函数:
说到宏,就不得不说一下宏和函数的区别:
1. 宏做的是简单的字符串替换(注意是字符串的替换,不是其他类型参数的替换),而函数的参数的传递,参数是有数据类型的,可以是各种各样的类型;
2. 宏的参数替换是不经计算而直接处理的,而函数调用是将实参的值传递给形参,既然说是值,自然是计算得来的;
3. 宏在编译之前进行,即先用宏体替换宏名,然后再编译的,而函数显然是编译之后,在执行时,才调用的.因此,宏占用的是编译的时间,而函数占用的是执行时的时间;
4. 宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参作为函数的局部变量,显然是占用内存的;
5. 函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主调函数,此时再恢复现场,这些操作,显然在宏中是没有的。
4. 宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参作为函数的局部变量,显然是占用内存的;
5. 函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主调函数,此时再恢复现场,这些操作,显然在宏中是没有的。
编译链接:
C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:
下面说一下编译链接的执行过程:
先说编译吧:编译是将高级语言转化为汇编的过程,编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
链接就得分了,根据开发人员指定的同库函数的链接方式的不同,一般是静态链接和动态连接。
静态链接:函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
动态链接:函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。