编译过程简介:
预编译:gcc -E file.c -o file.i
处理注释,以空格代替
将宏定义展开
处理条件编译指令
处理#include,展开被包含的文件
保留编译器需要使用的#pragma指令
编译: gcc -S file.i -o file.s
对预处理文件进行词法分析,语法 分析,语义分析
汇编:gcc -c file.s -o hello.o
将汇编代码翻译成机器码
链接:
将各个模块之间的相互引用的部分处理好,使得各个模块之间能够正确的衔接。将各个独立的模块链接成可执行的程序
静态链接:各个模块简单连接,编译期完成
动态链接:共享库文件,运行期完成
定义宏常量:
#define定义宏常量,可以出现咋代码的任何地方;从定义行开始,之后的代码都可使用这个宏常量(不管是否在某个函数中定义)
定义宏表达式:
1 #include <stdio.h> 2 int f1(int a, int b) 3 { 4 #define _MIN_(a,b) ((a)<(b) ? a : b) 5 return _MIN_(a, b); 6 } 7 int f2(int a, int b, int c) 8 { 9 return _MIN_(_MIN_(a,b), c); 10 } 11 int main() 12 { 13 printf("%d ", f1(2, 1)); 14 printf("%d ", f2(5, 3, 2)); 15 return 0; 16 }
宏表达式的域:自定义行开始之后的所有代码都可以使用
定义宏只能在一行中完成,(使用接续符换行)
宏表达式与函数的对比:
宏表达式在预编译的过程中被处理,编译器不知道宏表达式的存在;宏表达式用实参 完全代替形参,不进行任何运算;宏表达式没有任何的调用开销;宏表达式不能出现递归定义。
#define func(n) ((n>0) ? func(n-1) : 0 ); //错误,递归定义宏
#undef:用于结束宏的作用域
条件编译的使用:
条件编译是预编译指示命令,用于控制是否编译某段代码;执行行为类似:if else语句。
1 #if (exp1) 2 ....; 3 #else 4 ...; 5 #endif
#include:
本质:将已经存在的文件内容,复制到当前文件中。(预编译中完成)
1 // global.h 2 int global = 10; 3 4 // test.h 5 #include <stdio.h> 6 #include "global.h" 7 8 const char* NAME = "Hello world!"; 9 10 void f() 11 { 12 printf("Hello world! "); 13 } 14 15 // test.c 16 #include <stdio.h> 17 #include "test.h" 18 #include "global.h" 19 20 int main() 21 { 22 f(); 23 24 printf("%s ", NAME); 25 26 return 0; 27 }
编译test.c时,由于global.h同时在test.h中包含了,又在test.c中包含了,这样int global = 10;就在test.c中重复了两次,定义了两次,提示出错。
1 // global.h 2 #ifndef _GLOBAL_H_ 3 #define _GLOBAL_H_ 4 int global = 10; 5 #endif 6 7 // test.h 8 #ifndef _TEST_H_ 9 #define _TEST_H_ 10 #include <stdio.h> 11 #include "global.h" 12 const char* NAME = "Hello world!"; 13 void f() 14 { 15 printf("Hello world! "); 16 } 17 #endif 18 19 // test.c 20 #include <stdio.h> 21 #include "test.h" 22 #include "global.h" 23 24 int main() 25 { 26 f(); 27 printf("%s ", NAME); 28 return 0; 29 }
使用条件编译,可以保证前面包含进来的(复制进来)内容,不再重复复制,而不管你写了多个头包含语句。
#error和#line:
#error用于生成一个编译错误信息,并停止编译
#error message //message不需要使用双引号包围,message是程序员自定义的信息
#warring 用于生成警告,但不停止编译
1 #include <stdio.h> 2 #define CONST_NAME1 "CONST_NAME1" 3 #define CONST_NAME2 "CONST_NAME2" 4 int main() 5 { 6 #ifndef COMMAND 7 #warning Compilation will be stoped ... 8 #error No defined Constant Symbol COMMAND 9 #endif 10 printf("%s ", COMMAND); 11 printf("%s ", CONST_NAME1); 12 printf("%s ", CONST_NAME2); 13 return 0; 14 }
#line用于强制指定新的行号和编译文件名,并对源程序的代码重新编号
本质:是重定义_LINE_和_FINE_(内部宏定义)
用法:#line number filename
1 #include <stdio.h> 2 #line 14 "Hello.c" //__LINE__=14,即下一行行号为14;__FILE__="Hello.c" 3 #define CONST_NAME1 "CONST_NAME1" 4 #define CONST_NAME2 "CONST_NAME2" 5 void f() 6 { 7 return 0; // 该行行号为20,提示出错 8 } 9 int main() 10 { 11 printf("%s ", CONST_NAME1); 12 printf("%s ", CONST_NAME2); 13 printf("%d ", __LINE__); //27 14 printf("%s ", __FILE__); //hello.c 15 f(); 16 return 0; 17 }
#pragma:
#pragma是编译器指示字,用于指示编译器完成一些特定的动作;它所定义的很多指示字是编译器和操作系统特有的;在不同的编译器间不可移植。
预处理器将忽略它不认识的#pragma指令
两个不同的编译器可能以两种不同的方式解释同一条#pragma指令
#pragma message
#和##号运算符:
#用于在预编译期将宏参数转换为字符串,注意:是在预编译期完成
1 #include <stdio.h> 2 #define CONVERS(x) #x //#用于将CONVERS(x)中的x转换为x字符串 3 int main() 4 { 5 printf("%s ", CONVERS(Hello world!)); //Hello world! 6 printf("%s ", CONVERS(100)); //100 7 printf("%s ", CONVERS(while)); //while 8 printf("%s ", CONVERS(return)); //return 9 return 0; 10 }
1 #include <stdio.h> 2 #define CALL(f, p) (printf("Call function %s ", #f), f(p)) 3 int square(int n) 4 { 5 return n * n; 6 } 7 8 int f(int x) 9 { 10 return x; 11 } 12 13 int main() 14 { 15 printf("1. %d ", CALL(square, 4)); 16 // Call function square 17 //1.16 18 printf("2. %d ", CALL(f, 10)); 19 //Call function f 20 //2.10 21 return 0; 22 }
##用于在编译期粘连两个符号:
1 #include <stdio.h> 2 #define NAME(n) name##n 3 int main() 4 { 5 int NAME(1); 6 int NAME(2); 7 NAME(1) = 1; 8 NAME(2) = 2; 9 printf("%d ", NAME(1)); //1 10 printf("%d ", NAME(2)); //2 11 return 0; 12 }