Linux C语言 25-预处理操作
本节关键字:C语言编译过程、预处理、多行宏定义、通过宏判断操作系统、通过宏判断VC版本
相关C库函数:main,printf
什么是预处理?
预处理是C语言源码编译中重要的一步。用C语言编写的代码不能直接被计算机识别并运行,因此需要先将源码转换程计算机能够识别并运行的二进制语言,这个转换的过程叫做编译(翻译),编译过程主要分为四步:
- 预处理:进行的操作包含头文件展开、消除注释、宏定义替换、条件编译等。
gcc -E xxx.c -o xxx.i - 编译:将C语言代码转换成汇编语言。
gcc -S xxx.i -o xxx.s (汇编语言编程.asm) - 汇编:将汇编语言转化为可重定向目标文件(即可被链接),此时已经是二进制了,但还不是可执行文件。
as -C xxx.s -o xxx.o (单片机中变成.obj) - 链接:将自身程序与相关的库文件进行关联,形成真正的可执行文件。
gcc xxx.o -o xxx.out (Unix中默认为a.out,Windows中变成xxx.exe)
头文件展开
头文件包含的一般格式有两种:
- #include <文件名> 预处理程序直接检索C编译系统指定的目录。
- #include “文件名” 系统首先检索当前文件目录是否有该文件,如果没有,再检索C编译系统中指定的目录。
消除注释
C语言的注释符分为两种:“//”和“/* */”。编译器在预处理阶段,会将注释全部替换为空格。
- /* */ C风格注释,不推荐使用,因为在嵌套使用时会出现问题
- // C++风格注释,推荐使用
#include <stdio.h>int main(void)
{// // a/*hello*//*/*******/ */ // this is errorreturn 0;
}/**
编译时报错:
Linux_C_025.c: 在函数‘main’中:
Linux_C_025.c:14:3: 错误:expected expression before ‘/’ token
*/ // this is error
*/
宏定义替换
宏定义替换是指编译器将宏定义替换为定义时指定的文本内容,宏定义一般指以“#define”定义的语句,分为带参数和不带参数两种形式。
- #define 定义宏
- #undef 取消宏定义
#include <stdio.h>#define DEFINE_TESTint main(void)
{#ifdef DEFINE_TESTprintf("#ifdef: DEFINE_TEST\n");
#endif#if defined(DEFINE_TEST)printf("#if defined: DEFINE_TEST\n");
#endif#undef DEFINE_TEST#ifdef DEFINE_TESTprintf("#ifdef: DEFINE_TEST\n");
#endif#if defined(DEFINED_TEST)printf("#if defined: DEFINE_TEST\n");
#endifreturn 0;
}/** 运行结果:
#ifdef: DEFINE_TEST
#if defined: DEFINE_TEST
*/
不带参数宏
#include <stdio.h>#define PI 3.14int main(void)
{ int i;for (i=0; i<5; i++)printf("PI*%d=%.2f\n", i, i*PI);return 0;
}/** 运行结果:
PI*0=0.00
PI*1=3.14
PI*2=6.28
PI*3=9.42
PI*4=12.56
*/
带参数宏
原样替换,不会检测数据类型
#define SUM(a, b) ((a)+(b))
#include <stdio.h>#define SUM_1(a,b) a+b
#define SUM_2(a,b) (a)+(b)
#define SUM_3(a,b) ((a)+(b))int main(void)
{ printf("2*SUM_1(2,3)*2=%d\n", 2*SUM_1(2,3)*2); // 2*2+3*2printf("2*SUM_2(2,3)*2=%d\n", 2*SUM_2(2,3)*2); // 2*(2)+(3)*2printf("2*SUM_3(2,3)*2=%d\n", 2*SUM_3(2,3)*2); // 2*((2)+(3))*2return 0;
}/** 运行结果:
2*SUM_1(2,3)*2=10
2*SUM_2(2,3)*2=10
2*SUM_3(2,3)*2=20
*/
多行宏
#include <stdio.h>#define SUM(a,b) \do \{ \printf("a=%d, b=%d\n", a, b); \printf("%d+%d=%d\n", a, b, a+b); \} \while(0)int main(void)
{ SUM(6, 9);return 0;
}
/** 运行结果:
a=6, b=9
6+9=15
*/
常用宏
__DATE__ 当前源程序的创建日期
__FILE__ 当前源程序的文件名称(包括盘符和路径)
__LINE__ 当前被编译代码的行号
__STDC__ 返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C
__TIME__ 当前源程序的创建时间#line 修改下一行的行号
使用示例:
#include <stdio.h>int main(void)
{ printf("date: %s\n", __DATE__);printf("time: %s\n", __TIME__);printf("file: %s\n", __FILE__);printf("line: %d\n", __LINE__);printf("stdc: %d\n", __STDC__);printf("line: %d, #line=1\n", __LINE__);#line 1 "line_test.c"printf("file: %s, line: %d\n", __FILE__, __LINE__);#line 100 "one.c"printf("file: %s, line: %d\n", __FILE__, __LINE__);return 0;
}/** 运行结果:
date: Nov 24 2023
time: 14:44:24
file: Linux_C_025.c
line: 8
stdc: 1
line: 11, #line=1
file: line_test.c, line: 1
file: one.c, line: 100
*/
####“#”和“##”的区别
“#”和“##”是C语言中的预处理指令,它们只能在宏定义中使用。功能如下:
- #:当在宏定义中出现使用#参数的形式,就是将参数的字面值转换为字符串。如#define STR(s) #s ,这个宏作用就是把s的字面值转为字符串常量。
- ##:如果出现aa##bb##cc这种使用双井号定义的宏,作用就是形成一个新的符号aabbcc,注意是符号而不是字符串。
#include <stdio.h>#define TO_STR(s) ("_123_"#s"_321_\n")
#define MERGE(a,b) a##b
#define POWER(a,b) a##e##b
#define ARGV(x) argv##xint main(void)
{ int ARGV(1)=1, ARGV(2)=2, ARGV(3)=3;printf(TO_STR(hello world!));printf("%d\n", MERGE(12, 34));printf("\n");printf("POWER(2, 1)=%e\n", POWER(2, 1));printf("POWER(2, 1)=%d\n", POWER(2, 1));printf("POWER(2, 1)=%lf\n", POWER(2, 1));printf("\n");printf("ARGV(1)=%d, argv1=%d\n", ARGV(1), argv1);printf("ARGV(2)=%d, argv2=%d\n", ARGV(2), argv2);printf("ARGV(2)=%d, argv3=%d\n", ARGV(3), argv3);return 0;
}/** 运行结果:
_123_hello world!321
1234
POWER(2, 1)=2.000000e+01
POWER(2, 1)=2147483623
POWER(2, 1)=20.000000
ARGV(1)=1, argv1=1
ARGV(2)=2, argv2=2
ARGV(2)=3, argv3=3
*/
消除注释和宏替换的优先级
消除注释的优先级高于宏替换,我们可以使用下面的例程进行验证:
#include <stdio.h>
#define AAA //int main(void)
{AAA printf("Hello World\n");return 0;
}
/** 运行结果:
Hello World
上面的程序输出了“Hello World”,这就说明注释符没有生效“//”,那也就是说消除注释优先于宏替换。
整个过程就是,先将“//”替换成了空格,然后才进行宏替换,这样“AAA”就变成了空格,所以正常输出了“Hello World”。
条件编译
什么是条件编译?
一般情况下,源程序中所有的非注释行都需要参加编译。但是有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”。使用条件编译后,在预处理阶段,如果使用条件编译的代码块满足条件,则保留;如果不满足条件,则预处理器就会将代码裁剪,使其不会在后续阶段被编译。
C语言中常见的条件编译指令有:
- #ifdef 如果定义了,相当于“if define”
- #ifndef 如果没有定义,相当于“if not define”
- #if 判断是否定义,同if
- #else 同else
- #elif 同else if
- #if defined() 同#ifdef
- #endif 结束#if、#ifdef、#ifndef、#if defined()
条件编译的作用
1.我们可以使用条件编译来裁剪代码,用于快速实现某种目的,如版本维护(free,收费),功能裁剪以及代码的跨平台性。这样写出的软件一般只需维护一份代码,当制作者想要发行不同版本时,只需定义特定的宏就可以实现功能的改变。
2.一个C工程可能有多个.c文件,而.c文件可能又包含多个.h文件,难免会出现头文件重复包含的现象。这将会导致大量重复的代码被拷贝至我们的文件中,后期编译时会大大降低效率。因此我们可以使用条件编译来防止头文件重复包含,如下:
//头文件fun.h
#ifndef _FUN_H_
#define _FUN_H_// 需要定义的内容#endif
当没有包含过fun.h时,#ifndef _FUN_H_成立,定义_FUN_H_宏并保留后续内容。当未来有文件想再次包含时,由于_FUN_H_宏被定义,#ifndef _FUN_H_不成立,预处理器就将代码裁剪掉,不会进行编译,实现了预防头文件重复包含的作用。
此外,还可以使用 #pragma once 预处理指令防止头文件重复包含。
操作系统的识别
// The operating system, must be one of: (Q_OS_x)
DARWIN - Darwin OS (synonym for Q_OS_MAC)
SYMBIAN - Symbian
MSDOS - MS-DOS and Windows
OS2 - OS/2
OS2EMX - XFree86 on OS/2 (not PM)
WIN32 - Win32 (Windows 2000/XP/Vista/7 and Windows Server 2003/2008)
WINCE - WinCE (Windows CE 5.0)
CYGWIN - Cygwin
SOLARIS - Sun Solaris
HPUX - HP-UX
ULTRIX - DEC Ultrix
LINUX - Linux
FREEBSD - FreeBSD
NETBSD - NetBSD
OPENBSD - OpenBSD
BSDI - BSD/OS
IRIX - SGI Irix
OSF - HP Tru64 UNIX
SCO - SCO OpenServer 5
UNIXWARE - UnixWare 7, Open UNIX 8
AIX - AIX
HURD - GNU Hurd
DGUX - DG/UX
RELIANT - Reliant UNIX
DYNIX - DYNIX/ptx
QNX - QNX
LYNX - LynxOS
BSD4 - Any BSD 4.4 system
UNIX - Any UNIX BSD/SYSV system#if defined(_Win32) // 提示:WIN32有时不生效,_WIN32 或 _Win32才生效
#if defined(_linux)
#if defined(_unix)#ifdef __unix
# if (defined(__sun) || defined(__hpux) || defined(_AIX))
# endif
#endif#ifdef __linux
#endif#ifdef _WIN32
#endif
通过宏判断VC版本
#if _MSC_VER==1200// VC6
#else if _MSC_VER>1200// 更高的VC版本
#endif// _MSC_VER是MSVC编译器的内置宏,定义了编译器的版本。下面是一些编译器版本的_MSC_VER值
MS VC++ 10.0 _MSC_VER = 1600
MS VC++ 9.0 _MSC_VER = 1500
MS VC++ 8.0 _MSC_VER = 1400
MS VC++ 7.1 _MSC_VER = 1310
MS VC++ 7.0 _MSC_VER = 1300
MS VC++ 6.0 _MSC_VER = 1200
MS VC++ 5.0 _MSC_VER = 1100