这里写目录标题
预处理----编译----汇编----链接
预处理 | .h .c预处理后成.i |
---|---|
编译 | .i编译后生成汇编文件.s |
汇编 | .s汇编后生成目标文件.o |
链接 | .o链接生成可执行文件.exe |
预处理
- 展开我们所包含的头文件
- 注释删除
- 有宏的地方进行替换,并删除宏
- 不进行语法检查c->i
编译
将c语言代码翻译成汇编代码
- 进行语法,词法,语义分析
- 符号汇总
汇编
将汇编代码转换成了二进制指令 o文件此已经成为了二进制文件,但此时还不能运行
- 合并段表
- 符号表的合并和重定位
链接
将各自不同功能模块的.o文件集合到一起称为.exe文件,如果一个函数没有实现,预处理和编译阶段,系统不会报错,只有在链接的时候,才会报错
- 合并段表
- 符号表的合并和重定位
当一个工程中有多个.c和.h文件,经过预处理和编译后,会形成相应的.o文件,只有这些.o文件经过链接后,才会生成一个.exe文件
预定义符号
本身系统就定义好的符号,我们自己定义的不是预定义符号,叫定义符号
预处理指令:
以#开头,当编译器识别到该指令时,会在编译前对源代码做一些准备工作,即预处理
//__FILE__ //进行编译的源文件的路径
//__LINE__ //文件当前的行号
//__DATE__ //文件被编译的日期
//__TIME__ //文件被编译的时间
//__STDC__ //如果编译器遵循ANSI c(c标准),其值为1,否则未定义
#include<stdio.h>
int main ()
{
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
return 0;
}
预处理指令:预处理阶段所用到的指令,都以#开头
1.#define定义标识符:
#define MAX 1000
int main ()
{
int a=MAX;
printf("%d\n",a);//输出的是1000
return 0;
}
2.#define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中;
这种实现通常称为宏(macro)或定义宏(define macro)
声明宏的方式
#define name(parament-list) stuff
//将括号中的参数(括号里为参数)替换到内容(stuff)里去
//parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
注意: 参数列表的左括号必须与name紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
例1。
#define ADD(a,b) a+b
#define MUL(a,b) a*b
int main ()
{
int num=ADD(3,5)*MUL(2,7);//替换后num=3+5*2*7
//预处理器在程序中找到宏的示实例后,就会用替换体替换宏。从宏变成最终替换文本的过程称为 ‘宏展开’ 。
printf("%d\n" ,num); // 输出的结果是73,而不是112,
retuen 0;
}
例2.
#define DOUBLE(X) X+Y
int main ()
{
int a=5;
int ret=10*DOUBLE(a);
printf("%d\n",a);//输出是55,不是100
}
注:
- 一定要注意宏在替换时加括号,才不会因操作符的优先级改变最后结果;
- #define定义标识符时,后面最好不要加 “ ; ”
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的变量。对于宏,可以调用但是不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
printf("MAX=%d\n",MAX);//第一个MAX不会被替换,属于字符串常量
#:
(不是#define前的#)作用:将宏参数直接转换成字符串插入到字符串中
void print(int a)
{
printf("the value of a is %d\n",a);
}
int main()
{
int a=10;
int b=20;
print(a);
//输出the value of a is 10
print(b);
//输出the value of a is 20,并不是the value of b is 20
return 0;
}
三者输出都是hello word
两个字符串在一起时,会被当成一个字符串
int main()
{
printf("hello word");
printf("hello" " word");
printf("he" "llo word");
}
在x前加#,x不会替换成10,#x会变成"a";
也就是说:#x表示表达的内容就是其所对应的字符串(变成了"a")。
#define PRINT(X) printf("the value of" #X "is %d\n",X);
int main ()
{
int a=10;
int b=20;
PRINT(a);//the value of a is 10
PRINT(b);//the value of b is 20
return 0;
}
##:
作用:可以把位于它两边的符号合成一个符号;它允许宏定义从分离的文本片段创建标识符。
#define CAT(X,Y) X##Y
int main ()
{
int ab=1;
printf("%d\n",CAT(a,b));//输出是1
//a##b
//ab
//相当于变量ab
return 0;
}
#undef
作用:移除一个宏定义。
宏的作用域在#define和#undef之间
命令行定义
Linux环境下,arr[n],在编译的过程中,命令行设置一个参数,可随时改变n的值
条件编译
条件编译:需要的时候放开,不需要的时候删除,这也是预处理指令中的一种
(1).#ifdef与#endif
#include<stdio.h>
#define DEBUG //定义DEBUG
int main ()
{
int arr[0]={0,1,2,3,4};
int i=0;
for(i=0;i<5;i++)
{
arr[i]=0;
#ifdef DEBUG //如果DEBUG被定义,下一条语句参与编译
printf("%d\n",arr[i]);
#endif
}
return 0;
}
#ifndef//如果没有被定义就执行下语句
(2).#if
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
(3).多个分支的条件编译
同if…else…
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
(4).判断是否被定义
#include <stdio.h>
#define DEBUG 0
int main ()
{
#if define (DEBUG) //即使DEBUG值为0,但是它已经被定义过了,所以下一条语句可以执行
printf("hehe\n");
#endif
return 0;
}
(5).嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#end
#include包含的头文件
头文件的包含其实就是将.h文件下的内容,拷贝至,要引用它的文件下;每包含一次就拷贝一次
< > | 引库函数的头文件 |
---|---|
" " | 引自定义的头文件 |
两者查找策略也不同:
< >:是直接在标准路径下查找, 如果找不到就提示编译错误。
" ":先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件, 如果找不到就提示编译错误。
避免头文件的重复引入
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者:
#pragma once