Linux C语言 25-预处理操作

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 

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”定义的语句,分为带参数和不带参数两种形式。

  • #define 定义宏
  • #undef 取消宏定义
#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语言中的预处理指令,它们只能在宏定义中使用。功能如下:

  • #:当在宏定义中出现使用#参数的形式,就是将参数的字面值转换为字符串。如#define STR(s) #s ,这个宏作用就是把s的字面值转为字符串常量。
  • ##:如果出现aa##bb##cc这种使用双井号定义的宏,作用就是形成一个新的符号aabbcc,注意是符号而不是字符串。
#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语言中常见的条件编译指令有:

  • #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

你可能感兴趣的:(Linux_C语言,linux,c语言,运维,开发语言)