一、编译器如何处理C源文件(source file)。
1)、以某种编码方式替换源文件中的字符,例如:用换行符替换OS里的行尾指示符。
不同OS里的行尾指示符是不同的,例如Windows 是\r\n
,UNIX 是 \r
,MAC OS 是\n
。
如:在8086汇编中,我们在DOS下编写输出程序经常需要执行 向内存中添加 0AH,0DH。
用来进行换行操作,其中0AH转换为ASCII码其实现的功能就是\r,0DH实现的功能就是\n。
\r 为 回车 在C程序中,如果输出字符中使用了\r,其效果为光标移动到----本行开始的位置。
\n 为换行 意思为光标移动到下一行的
2)、有时候我们会因为一条语句需要进行多行去书写,那么就会在语句中间添加一个 \ 然后进行换行继续编写,编译器就会把这个\去掉并且去掉换行符,并且将下一行与本行连接起来,实现一个逻辑行。
3)、将源文件分解为注释部分、空白字符序列、一些预处理记号。
预处理记号:
1、头文件 例如 <stdio.h> 或者 “ myheader.h”
2、字面值常量
3、标识符
4、预处理 常量
5、运算符 与 标点。
6、其它非空白字符。
其实可以把C语言的组成部分看成是一些记号的集合,例如:
printf("Hello World. %d",var_1);
printf (标识符) ( "Hello World" (字符串字面量) , var_1(标识符) ) ; 其它为标点。
用一个空格替换一段注释。
保持换行符。
编译器在处理我们的源文件中,会尽最大处理预处理记号。
例如
int var_1=1;
int var_2=0xE+var_1; //WRONG
int var_3=0xE + var_1;
当处理第二条语句中, 会尽最大可能处理这些记号,那么0xE+var_1会被处理成一个整数常量,可是这个常量是非法的。所以会出现错误。而第二个会被分解成 0xE 、+ 和 var_1 三部分,所以它是正确的。在一些编辑器中当我们换行时,会自动添加空格在不同预处理记号之间(例如:Visual Studio )。
但是在头文件中可避免这中意外的发生,因为头文件只在#include 与 #pragma 中会发生定义行为。
例如:
4)、
1、进行预处理(下面会详细介绍什么是预处理)
2、#include所调用的文件执行上面所有操作。
3、完成这些操作后文件移除所预处理指令。
5)、字符常量与字符串常量和转移序列转换成可执行字符。
6)、连接相邻的字符串字面量。
7)、编译:分析语义和语法翻译成翻译单元。
8)、进行链接:将翻译单元和外部引用单元进行连接成OS可执行文件。
二、预处理器
baidu : 预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写)替代。
wiki : A common example from computer programming is the processing performed on source code before the next step of compilation. In some computer languages (e.g., C and PL/I) there is a phase of translation known as preprocessing. It can also include macro processing, file inclusion and language extensions.
预处理指令
条件编译
//对某个macro进行判断,如果定义了执行相应代码块,如果没有执行下一个条件预处理
//与ifdef相反
//这个条件预处理块终止符
//不是针对define 而是一个表达式
/*
#if !defined(NAME)
#define COLOR "Blue"
#else
#define COLOR "Red"
*/
宏替换
我们调用#define DEMO " This is a demo"
在我们的程序中每当遇见DEMO 就会替换成"This is a demo "
而#undef意思为取消我们之前定义的宏
包含其它文件
则遇见#include <head_file>或者 #include" myhead.h" 会查找该文件的位置并且放入源文件相应指令后面的位置。
1、#include<> 其搜索路径为标准库目录
2、#include" " 其第一次搜索目录为当前源文件目录下,如果没有搜索到,再去搜索标准库目录
错误指令
若使用#error 则会引发错误,是程序不能继续编译。并且告诉为什么。
例如:
int main()
{
printf("Hello World!\n");
return 0;
}
RUN:
xiandonghua@HappyDay:~/c$ gcc demo.c -o demo
demo.c:8:2: error: #error "Sorry!"
#error "Sorry!"
^
如果我们只把 #if DEMO == 22 改成 #if DEMO == 33 其它代码不变。
RUN:
xiandonghua@HappyDay:~/c$ gcc demo.c -o demo
xiandonghua@HappyDay:~/c$ ./demo
Hello World!
实现定义行为#pragma
使用#pragma 控制编译器的行为,例如 禁用编译器警告和更改对其要求。
相对其他预处理,pragma想对比较复杂,它的语法:
//parament 为参数
一般使用的指令
//在编译过程中会打印字符内容
//设置程序中函数代码存放的代码段(code segment).
//源文件中调用的头文件有且仅只调用一次,因为有时候有的头文件会被间接重复调用
//表示到目前为止之后的头文件不再编译
//不再显示为1234序号的警告,VS 里会有这种序号,但GCC 目前没有找到
文件名和行信息 (...........)
使用#line 行号 或者 #line 行号 文件名
如果在某一位置使用#line 999 那么它的下一行行号为999,之后每行+1。
如果在某一位置使用#line 999 同上另外编译器会提示你的文件名不再是你运行的文件名。(可执行文件名没有更改,只是编译器所报的文件名更改)
学习,学一点,是一点。