编译预处理
来源:互联网 发布:一搜网络 健身 编辑:程序博客网 时间:2024/06/08 05:55
转载前注明出处,欢迎转载分享
程序执行前
程序执行前经历了:预处理器->编译器->汇编器->链接器
1)预处理过程:
- 处理所有注释,以空格代替
- 将所有#define删除,并展开所有宏定义
- 处理条件编译指令#if,#ifdef,#elif,#else,#endif
- 处理#include,展开被包含的文件
- 保留编译器需要使用的#pragma指令
预处理指令:
gcc - E file.c - o hello.i
2)编译过程:
- 对预处理文件进行一系列词法分析,语法分析和语义分析
- 分析结束后进行代码优化生成相应的汇编代码文件
编译指令:
gcc - S file.c - o hello.s //gcc -S hello.i -o hello.s或由生成的hello.i生成hello.s
3)汇编过程:
- 汇编器将汇编代码转变为机器可以执行的指令
- 每个汇编语句几乎都对应一条机器指令
汇编指令:
gcc - c hello.s - o hello.o //gcc -c file.c -o hello.o
注意:因为.o文件内部为机器码(即二进制文件),所以生成的.o文件无法打开
4)链接器:
- 链接器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。
静态链接:将库文件与.o文件绑定在一起(可能一个库拷贝给多个可执行程序)
缺点:占内存
动态链接:库文件在内存中存放,是共享的而不是程序的一部分,生成的.o文件在内存中找库文件然后生成可执行程序
缺点:每次执行程序需要找库文件,虽然节省内存,但是比静态链接耗时很多
小结:
编译器将编译工作主要分为预处理,编译和汇编三部
链接器的工作是把各个独立的模块链接为可执行程序
静态链接在编译期完成,动态链接在运行期间完成
---------------------------------------------------------------------------------------------------
宏表达式
- 宏表达式在预编译期被处理,编译器不知道宏表达式的存在
- 宏表达式用“实参”完全替代形参,不进行任何运算
- 宏表达式没有任何的“调用”开销
- 宏表达式不能出现递归定义
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include < stdio.h >
#define DIM(array) ( sizeof(array) / sizeof(*array))
intdim ( int array[])
{
return sizeof(array) / sizeof(*array);
}
intmain()
{
int a[] = { 1, 2, 3, 4, 5};
printf( "%d\n", dim(a));
printf( "%d\n", DIM(a));
return 0;
}
int
{
}
int
{
}
上述代码则体现该宏定义的优势是函数无法实现的,函数形参intarray[]在接收参数的时候被当做是指针的形式,所以函数返回的结果为1,但是宏只是简单的替换,计算出的结果就是数组元素的个数。
宏定义的常量或表达式是否有作用域的限制?看如下代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include < stdio.h >
int f1( int a, int b)
{
#define MIN(a,b) ((a) < (b) ? a : b)
return MIN(a, b);
//#undef MIN(a,b)
}
intf2( int a, int b, int c)
{
return MIN(MIN(a, b), c);
}
int main()
{
printf( "%d\n", f1( 2, 1));
printf( "%d\n", f2( 5, 3, 2));
return 0;
}
{
int
{
}
{
}
通过运行结果可知该代码说明宏定义不存在作用域的限制,如果我们想要对上述代码中宏定义进行作用范围的限制,则在上述代码中去掉注释,加入#undefMIN,即可限定该宏在f1中可用,此时运行上述代码时会提示f2中MIN未定义。
强大的内置宏:
宏日志示例代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include < stdio.h >
#include < time.h >
#define LOG(s) do{ \
time_t t; \
struct tm* ti; \
time(&t); \
ti = localtime(&t); \
printf( "%s[%s:%d] %s\n" , asctime(ti), __FILE__, __LINE__, s); \
}
void func()
{
LOG( "Enter func()..." );
LOG( "Exit func()..." );
}
intmain()
{
LOG( "Enter main()..." );
func();
LOG( "Exit main()..." );
return 0;
}
}
{
}
int
{
}
头文件time.h所包含的库函数和库宏:
参考资料:http://wiki.jikexueyuan.com/project/c/c-standard-library-time-h.html
#define f (x) ((x)- 1)
对于上面的定义所代表的意思:
宏是简单的替换,实际上只有紧挨#define的那一块被替换为后面的部分即是将f替换成(x)((x)-1),尽管后面由多个空格隔开也不要紧,因为当宏经过预处理被替换到代码中时,我们知道空格不影响代码的运行。
------------------------------------------------------------------------------------------------
条件编译
条件编译的基本概念:
- 条件编译的行为类似于C语言中的if..else
- 条件编译是预编译指示命令,用于控制是否编译某段代码
如下代码即为条件编译:
2
3
4
5
6
7
8
9
10
11
#include < stdio.h >
int main()
{
#if(C == 1)
printf( "this is first printf...\n" );
#else
printf( "this is second printf...'\n" );
#endif
return 0;
}
{
#else
#endif
}
我们可以用gcc -E test.c -otest.i生成预编译文件,然后查看这段代码时main函数只有第一个printf,上述代码与普通的if..else语句不同点在于其处理在预编译时期即改变。但是如果用gcc-DC=0 -E test.c -otest.c时我们会发现预编译时保留的是第二个printf语句,这里说一下-DC=0的意思,即是将C定义为0。
#include的本质是将已经存在的文件内容嵌入到当前文件中
#include间接包含同样会产生嵌入文件内容的动作
那么间接包含同一个头文件就会产生编译错误,因为可能某个头文件声明的全局变量被声明多次,例如在global.h中声明int a=10;然而test.h头文件中也包含了global.h头文件,那么当test.c中既包含了global.h又包含了test.h的时候,则相当于调用了两次global.h,声明了两次全局变量a,那么肯定会出现编译错误,改进格式如下:
2
3
4
5
6
#ifndef _TEST_H_
#define _TEST_H_
//头文件内容
#endif
#endif
保持如上格式后即可随心所欲调用头文件多次而不产生重复和报错。
条件编译的意义:
- 条件编译使得我们可以按不同的条件编译不同的代码段,因而可以产生不同的目标代码
- #if..#else..#endif被预编译器处理;而if..else语句被编译器处理,必然被编译进目标代码
- 实际工程中条件编译主要用于以下两种情况:
不同的生产线公用一份代码(同一份代码可以产生不同生产线)
区分编译产品的调试版和发布版
用于不同生产线的示例代码如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include < stdio.h >
#ifndef DEBUG
#define LOG(s) printf( "[%s:%d] %s\n" , __FILE__, __LINE__, s);
#else
#defineLOG(s) NULL
#endif
#ifdefHIGH
void f()
{
printf( "this is the high level product!\n" );
}
#else
voidf()
{
}
#endif
intmain()
{
LOG( "enter main()..." );
f();
printf( "1.query information.\n" );
printf( "2.record information.\n" );
printf( "3.delete information.\n" );
#ifdefHIGH
printf( "4.high level query.\n" );
printf( "5.mannul service.\n" );
printf( "6.exit.\n");
#else
printf( "4.exit.\n");
#endif
LOG( "exit main()..." );
return 0;
}
#define
#endif
#ifdef
{
}
#else
void
{
}
int
{
#ifdef
#else
#endif
}
切换至DEBUG模式:
gcc - DDEBUG test.c
运行结果:
[test.c:22] enter main()...
1.query information.
2.record information.
3.delete information.
4.exit.
[test.c:30] exit main()...
如果切换至普通模式:
gcc test.c
运行结果:
1.query information.
2.record information.
3.delete information.
4.exit.
切换至高级模式:
gcc -DHIGH test.c
运行结果:
this is the high level product!
1.query information.
2.record information.
3.delete information.
4.high level query.
5.mannul service.
6.exit.
还可以切换至高级DEBUG模式:
[test.c:22] enter main()...
this is the high level product!
1.query information.
2.record information.
3.delete information.
4.high level query.
5.mannul service.
6.exit.
[test.c:38] exit main()...
小结:
- 通过编译器命令行能够定义预处理器使用的宏
- 条件编译可以避免重复包含头同一个头文件
- 条件编译是在工程开发中可以区别不同产品线的代码
- 条件编译可以定义产品的发布版和调试版
--------------------------------------------------------------------------------------------------
#error
用于生成一个编译错误消息,并停止编译
用法:
#error message
注:message不需要用双引号包围
#error编译指示字用于自定义程序员特有的编译错误消息
类似的,#warning用于生成编译警告,但不会停止编译
示例代码:
2
3
4
5
6
7
8
9
10
11
12
#include < stdio.h >
int main()
{
#ifndef COMMAND
#warningCompilation be stoped...
#error No defined Constant Symbol COMMAND
#endif
printf( "%s\n", COMMAND);
return 0;
}
{
#warning
}
如果直接编译:gcc test.c,则会报错停止。
若果编译时进行宏定义:
gcc - DCOMMAND = \"TestCommand\" test.c
即意思是定义宏COMMAND为字符串"TestCommand"(我们可以在预处理.i文件中查看其变化),此时编译运行正常。
#line
用于强制指定新的行号和编译文件名,并对源程序的代码重新编号
用法:
#line number filename
注:filename可省略
#line编译指示字的本质是重定义__LINE__和__FILE__
示例代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include < stdio.h >
#line 14 "Hello.c"
voidf()
{
return 0;
}
intmain()
{
printf( "%d\n", __LINE__);
printf( "%d\n", __FILE__);
f();
return 0;
}
void
{
}
int
{
}
代码结果为:
20
Hello.c
因为从#line的下一行开始本来应该为第4行,但我们指定下一行为14行,所以打印__LINE__的行号原本为第10行,现在则变为第20行。并且__FILE__打印出来的是我们用#line宏所指定的文件名。
-----------------------------------------------------------------------------------------------
#和##运算符的使用解析
#运算符
我们知道#是预处理指令的开始符,但是它还有另一种作用,#运算符还可以用于在预编译期将宏参数转换为字符串。
示例代码:
2
3
4
5
6
7
8
9
10
11
12
13
#include < stdio.h >
#define CONVERS(x) #x
int main()
{
printf( "%s\n", CONVERS(Hello world!));
printf( "%s\n", CONVERS( 100));
printf( "%s\n", CONVERS( while));
printf( "%s\n", CONVERS( return));
return 0;
}
{
}
输出结果:
Hello world!
100
while
return
分布编译的话查看.i文件时,main中输出语句为:
printf("%s\n", "Hello world!");
printf("%s\n", "100");
printf("%s\n", "while");
printf("%s\n", "return");
宏还可以在调用函数之前打印所调用函数的名称(利用#打印字符串的作用来实现)
代码如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include < stdio.h >
#define CALL(f, p) (printf( "Call function %s\n" , #f), f(p)) //逗号表达式的使用
intsquare( int n)
{
return n * n;
}
int f( int x)
{
return x;
}
int main()
{
printf( "%s\n", CALL(square, 4));
printf( "%s\n", CALL(f, 10));
return 0;
}
int
{
}
{
}
{
}
生成.i文件后主函数两条输出语句变为:
printf("1. %s\n", (printf("Call function %s\n", "square"),square(4)));
printf("2. %s\n", (printf("Call function %s\n", "f"),f(10)));
##运算符
用于在预编译期粘连两个符号,示例代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include < stdio.h >
#define NAME(n) name##n
int main()
{
int NAME( 1);
int NAME( 2);
NAME( 1) = 1;
NAME( 2) = 2;
printf( "%d\n", NAME( 1));
printf( "%d\n", NAME( 2));
return 0;
}
{
}
输出结果:
1
2
通过单步编译生成.i文件查看其预处理时发现主函数如下:
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
int name1;
int name2;
name1 = 1;
name2 = 2;
printf( "%d\n", name1);
printf( "%d\n", name2);
return 0;
}
{
}
以上为##粘连符的使用方法,看如下代码巧妙宏定义结构体
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include < stdio.h >
#define STRUCT(type) typedef struct _tag_##type type;\
struct _tag_##type
STRUCT(Student)
{
char *name;
int id;
}
int main()
{
Student s1;
Student s2;
s1.name = "s1";
s1.id = 0;
s2.name = "s2";
s2.id = 1;
printf( "%s\n", s1.name);
printf( "%s\n", s1.id);
printf( "%s\n", s2.name);
printf( "%s\n", s2.id);
return 0;
}
STRUCT(Student)
{
}
{
}
预处理后的结构体实际是:
2
3
4
5
6
typedef struct _tag_Student Student;
struct _tag_Student
{
char *name;
int id;
}
{
}
0 0
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- 编译预处理
- C语言符号技巧总结
- Linux-Centos下的git配置使用
- gcc与g++的区别(转贴)
- 关于Git的安装
- linux内核进程创建分析
- 编译预处理
- 动态申请内存及相关补充
- Mongodb解析
- C/S架构的简单文件传输系统的实现
- I/O多路复用之select函数分析
- Java进阶之reflection(反射机制)
- I/O多路复用之poll函数分析
- 【NOI2016】循环之美,mobius反演+杜教筛
- 标志寄存器及其标志位