本节关键字:C语言编译过程、预处理、多行宏定义、通过宏判断操作系统、通过宏判断VC版本
相关C库函数:main,printf
预处理是C语言源码编译中重要的一步。用C语言编写的代码不能直接被计算机识别并运行,因此需要先将源码转换程计算机能够识别并运行的二进制语言,这个转换的过程叫做编译(翻译),编译过程主要分为四步:
头文件包含的一般格式有两种:
C语言的注释符分为两种:“//”和“/* */”。编译器在预处理阶段,会将注释全部替换为空格。
#include
int main(void)
{
// // a
/*
hello
*/
/*
/*
*****
*/
*/ // this is error
return 0;
}
/**
编译时报错:
Linux_C_025.c: 在函数‘main’中:
Linux_C_025.c:14:3: 错误:expected expression before ‘/’ token
*/ // this is error
*/
宏定义替换是指编译器将宏定义替换为定义时指定的文本内容,宏定义一般指以“#define”定义的语句,分为带参数和不带参数两种形式。
#include
#define DEFINE_TEST
int main(void)
{
#ifdef DEFINE_TEST
printf("#ifdef: DEFINE_TEST\n");
#endif
#if defined(DEFINE_TEST)
printf("#if defined: DEFINE_TEST\n");
#endif
#undef DEFINE_TEST
#ifdef DEFINE_TEST
printf("#ifdef: DEFINE_TEST\n");
#endif
#if defined(DEFINED_TEST)
printf("#if defined: DEFINE_TEST\n");
#endif
return 0;
}
/** 运行结果:
#ifdef: DEFINE_TEST
#if defined: DEFINE_TEST
*/
#include
#define PI 3.14
int 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
#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*2
printf("2*SUM_2(2,3)*2=%d\n", 2*SUM_2(2,3)*2); // 2*(2)+(3)*2
printf("2*SUM_3(2,3)*2=%d\n", 2*SUM_3(2,3)*2); // 2*((2)+(3))*2
return 0;
}
/** 运行结果:
2*SUM_1(2,3)*2=10
2*SUM_2(2,3)*2=10
2*SUM_3(2,3)*2=20
*/
#include
#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
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语言中的预处理指令,它们只能在宏定义中使用。功能如下:
#include
#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##x
int 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
#define AAA //
int main(void)
{
AAA printf("Hello World\n");
return 0;
}
/** 运行结果:
Hello World
上面的程序输出了“Hello World”,这就说明注释符没有生效“//”,那也就是说消除注释优先于宏替换。
整个过程就是,先将“//”替换成了空格,然后才进行宏替换,这样“AAA”就变成了空格,所以正常输出了“Hello World”。
一般情况下,源程序中所有的非注释行都需要参加编译。但是有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”。使用条件编译后,在预处理阶段,如果使用条件编译的代码块满足条件,则保留;如果不满足条件,则预处理器就会将代码裁剪,使其不会在后续阶段被编译。
C语言中常见的条件编译指令有:
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
#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