C/C++ preprocessor commands

The source code of a C program may include various compilation instructions, which are called preprocessing instructions. Preprocessor commands belong to the C language compiler, not part of the C language. The C language preprocessor includes the following commands: #define, #error, #include, #if, #else, #elif, #endif, #ifdef, #ifndef, #undef, #line, #pragma, etc. In general, preprocessing directives can be divided into four categories:

  • 宏:#define、#undef
  • file: #include
  • Conditional compilation: #if, #else, #elif, #endif, #ifdef, #ifndef, etc.
  • Others: #error, #line and #pragma are some special commands that are rarely used.

Preprocessing directives follow these rules:

  • Commands start with #. The # sign does not need to be at the beginning of a line, as long as it is preceded by a whitespace character. After the # is the command name, followed by other information required by the command.
  • Any number of spaces or horizontal tabs can be inserted between the symbols of a directive.
  • Instructions always end at the first newline unless explicitly indicated to continue.
  • Instructions can appear anywhere in a program. It is common to put #define and #include directives at the beginning of the file, and other directives later, even in the middle of function definitions.
  • Comments can be placed on the same line as directives.

The above rules can be introduced in chapter 6.10 of the C99 standard document.

0x01 Macro instruction #define

The only thing to notice is that there is no semicolon in this statement . There can be any number of spaces between the identifier and the string, and once the string begins, it is only terminated by a new line.

0x02 file directive #include

The function of the #include command is to insert the contents of the specified file module into the location where #include is located. When the program is compiled and linked, the system will link all the files specified by #include to generate executable code.

(1) If the explicit pathname is part of the file identifier, only those subdirectories are searched for the embedded file. Otherwise, if the filename is enclosed in double quotes , the current working directory is retrieved first. If no file is found, all directories specified on the command line are searched. If the file is still not found, the standard directories defined by the implementation are searched. If there is no explicit pathname and the filename is enclosed in angle brackets , it is first retrieved within the directory on the compile command line. If the file is not found, the standard directory is searched, not the current working directory.

(2) Other files can also be included in the file module included through #define. This usage is called nested inclusion. The number of nested layers is related to the specific C language system, but generally more than 8 layers can be nested.

(3) #include can also include .c files. But generally not so

The following is an example of gcc under Linux:

#include "headfile.h"

①Search the current directory first

②Then search the directory specified by -I

③ Then search for the environment variable CPLUS_INCLUDE_PATH of gcc (C program uses C_INCLUDE_PATH)

④ Finally search the default directory of gcc

/usr/include

/usr/local/include

/usr/lib/gcc/x86_64-redhat-linux/4.1.1/include

#include <headfile.h>

①First search the directory specified by -I

② Then search for the environment variable CPLUS_INCLUDE_PATH of gcc

③ Finally search the default directory of gcc

/usr/include

/usr/local/include

/usr/lib/gcc/x86_64-redhat-linux/4.1.1/include

Library search path:

①gcc will look for -L

②Find the environment variable LIBRARY_PATH of gcc again

③ Find the default directory /lib, /usr/lib, /usr/local/lib

0x03 Conditional compilation

The following content is from a technical blog

1、#if, #elif, #else, #endif

#if 条件1
 代码段1
#elif
条件2
 
代码段2
...
#elif
条件n
 
代码段n
#else
 
代码段 n+1
#endif

即可以设置不同的条件,在编译时编译不同的代码,预编译指令中的表达式与C语言本身的表达式基本一至如逻辑运算、算术运算、位运算等均可以在预编译指令中使用。之所以能够实现条件编译是因为预编译指令是在编译之前进行处理的,通过预编译进行宏替换、条件选择代码段,然后生成最后的待编译代码,最后进行编译。

#if的一般含义是如果#if 后面的常量表达式为true,则编译它所控制的代码,如条件1成立时就代码段1,条件1不成立再看条件2是否成立,如果条件2成立则编译代码段2,否则再依次类推判断其它条件,如果条件1-N都不成力则会编译最后的代码段n+1。

2、#ifdef, #else, #endif或#ifndef, #else, #endif

条件编译的另一种方法是用#ifdef与#ifndef命令,它们分别表示"如果有定义"及"如果无定义"。有定义是指在编译本段代码时是否有某个宏通过 #define 指令定义的宏,#ifndef指令指找不到通过 #define 定义的某宏,该宏可以是在当前文件此条指令的前面定义的,也可以是在该文件包含的其它文件中。

#ifdef的一般形式是:

#ifdef macro_name
 
代码段1
#else
 
代码段2
#endif

#ifndef的一般形式是:

#ifndef macro_name
 
代码段2
#else
 
代码段1
#endif

这两段代码的效果是完全一样的。

3、通过宏函数defined(macro_name)

参数为宏名(无需加""),如果该macro_name定义过则返回真,否则返回假,用该函数则可以写比较复杂的条件编译指令,如#if defined(macro1)||(!defined(macro2) && defined(macro3))...#else...#endif

(1)#ifdef和#defined()比较

首先比较一下这两种方法,第一种方法只能判断一个宏,如果条件比较复杂实现起来比较烦锁,用后者就比较方便。如有两个宏MACRO_1,MACRO_2只有两个宏都定义过才会编译代码段A,分别实现如下:

#ifdef MACRO_1

#ifdef MACRO_2

代码段 A

#endif

#endif

或者

#if defined(MACRO_1) && defined(MACRO_2)

#endif

同样,要实现更复杂的条件用#ifdef更麻烦,所以推荐使用后者,因为即使当前代码用的是简单的条件编译,以后在维护、升级时可能会增加,用后者可维护性较强。旧的编译器可能没有实现#defined()指令,C99已经加为标准。要兼容老的编译器,还需用#ifdef指令。

2、#if与 #ifdef或#if defined()比较

比如自己写了一个printf函数,想通过一个宏MY_PRINTF_EN实现条件编译,用#if可实现如下

#define MY_PRINTF_EN 1
#if MYS_PRINTF_EN == 1
int printf(char* fmt, char* args, ...)
{

...

}

#endif

如果宏MY_PRINTF_EN定义为1则编译这段代码,如果宏定义不为1或者没有定义该宏,则不编译这段代码。同样也可以通过#ifdef或者#defined()实现,如

#define MY_PRINTF_EN 1
#if defined(MY_PRINTF_EN)
int printf(char* fmt, char* args, ...)
{

...

}

#endif

在这种情况下两种方法具有异曲同工之妙,但试想如果你为了节约代码写了两个printf函数,在不同情况下使用不同的printf函数,一个是精简版一个是全功能标准版,如:

#define MY_PRINTF_SIMPLE

#ifdef MY_PRINTF_SIMPLE

void printf(*str) // 向终端简单地输出一个字符串

{

...

}

#endif

#ifdef MY_PRINTF_STANDARD

int printf(char* fmt, char* args, ...)

{

...

}

#endif

同样可以用#if defined()实现

#define MY_PRINTF_SIMPLE

#if defined(MY_PRINTF_SIMPLE)

void printf(*str) // 向终端简单地输出一个字符串

{

...

}

#elif defined(MY_PRINTF_STANDARD)

int printf(char* fmt, char* args, ...)

{

...

}

#endif

两种方法都可以实现,但可见后者更方便。但试想如果你有三个版本,用前者就更麻烦了,但方法相似,用后者就更方便,但仍需三个宏进行控制,你要住三个宏,改进一下就用#if可以用一个宏直接控制N种情况如:

#define MY_PRINTF_VERSION    1

#if MY_PRINTF_VERSION == 1

void printf(*str) // 向终端简单地输出一个字符串

{

...

}

#elif MY_PRINTF_VERSION == 2

 int printf(char* fmt, char* args, ...)

{

...

}

#elif MY_PRINTF_VERSION == 3

int printf(unsigned char com_number, char* str)

{

...

}

#else

默认版本

#endif

这样,你只需修改一下数字就可以完成版本的选择了

看来好像用#if 比较好了,试想如下情况:你写了一个配置文件叫做config.h用来配置一些宏,通过这些宏来控制代码,如你在config.h的宏#define MY_PRINTF_EN 1来控制是否需要编译自己的printf函数,而在你的源代码文件printf.c中有如下指令

#include "config.h"

#if MY_PRINTF_EN == 1

int printf(char* fmt, char* args, ...)

{

...

}

#endif

但这样也会有一个问题,就是如果你忘了在config.h中添加宏MY_PRINTF_EN,那么自己写的printf函数也不会被编译,有些编译器会给出警告:MY_PRINTF_EN未定义。如果你有两个版本的想有一个默认版本,可以在printf.c中这样实现

#incldue "config.h"

#if !defined(MY_PRINTF_VERSION)

  #define MY_PRINTF_VERSION   1

#endif

#if MY_PRINTF_VERSION == 1

void printf(*str) // 向终端简单地输出一个字符串

{

...

}

#elif MY_PRINTF_VERSION == 2

int printf(char* fmt, char* args, ...)

{

...

}

#elif MY_PRINTF_VERSION == 3

int printf(unsigned char com_number, char* str)

{

}

#endif

这种情况下还得用到#ifdef或#if defined(),你可以不用动主体的任何代码,只需要修改printf.c文件中MY_RPINTF_VERSION宏的数字就可以改变了,如果用前面那种方法还得拖动代码,在拖动中就有可能造成错误。

再试想,如果软件升级了,或者有了大的改动,原来有三个版本,现在只剩下两个版本了,如

#if MY_PRINTF_VERSION == 2

int printf(char* fmt, char* args, ...)

{...

}

#elif MY_PRINTF_VERSION == 3

int printf(unsigned char com_number, char* str)

{

}

#endif

因为这些核心代码不想让使用这些代码的人关心,他们只需要修改config.h文件,那就要在printf.c中实现兼容性。如果以前有人在config.h配置宏MY_PRINTF_VERSION为1,即有#define MY_PRINTF_VERSION   1,而现在没有1版本了,要想兼容怎么办?那当然可以用更复杂的条件实现如:

#if MY_PRINTF_VERSION == 2 || MY_PRINTF_VERSION == 1

int printf(char* fmt, char* args, ...)

{

...

}

#elif MY_PRINTF_VERSION == 3

int printf(unsigned char com_number, char* str)

{

}

#endif

不过还有另外一种方法,即使用#undef命令

#if MY_PRINTF_VERSION == 1

  #undef MY_PRINTF_VERSION

  #define MY_PRINTF_VERSION  2

#endif

#if MY_PRINTF_VERSION == 2

int printf(char* fmt, char* args, ...)

{

...

}

#elif MY_PRINTF_VERSION == 3

int printf(unsigned char com_number, char* str)

{

}

#endif

用#if还有一个好处,如果你把宏名记错了,把MY_PRINTF_EN定义成了MY_PRINT_EN,那么你用#ifdef MY_PRINTF_EN或者#if defined(MY_PRINTF_EN)控制的代码就不能被编译,查起来又不好查,用#if MY_PRINTF_EN ==1控制就很好查,因为你把MY_PRINTF_EN定义成MY_PRINT_EN,则MY_PRINTF_EN实际上没有定义,那么编译器会给出警告#if MY_PRINTF_EN == 1中的MY_PRINTF_EN没有定义,但错就比较快。

0x04 其他

 

 

 

 

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325386359&siteId=291194637