当前位置: C语言 -- 基础 -- 预处理指令

预处理指令(三)

三、源文件包含

源文件包含(source file inclusion)是指编译前使用#include预处理指令将指定文件的全部内容复制并粘贴到该指令所在位置的过程。#include预处理指令应标识一个可由实现处理的头文件(header file)或者源文件(source file)。

#include <stdio.h>
#include "test.c"

#include预处理指令具有以下三种语法格式:

第一种格式:

#include <h-char-sequence> new-line

其中include是指令名;h-char-sequence是头文件字符序列,头文件字符序列中的字符可以是源字符集中除换行符和>字符外的任何字符;new-line是换行符。

该格式指令将在实现定义的位置搜索由< >内的特定序列标识的头文件,并用头文件内容替换该指令。位置如何指定以及头文件如何识别将由实现定义。


第二种格式:

#include "q-char-sequence" new-line

其中include是指令名;q-char-sequence是引号字符序列(quote character sequence),引号字符序列中的字符可以是源字符集中除换行符和"字符外的任何字符;new-line是换行符。


该格式指令对命名源文件的搜索以实现定义的方式进行。如果不支持这种搜索或者搜索失败,该指令会被重新处理,就像读取以下格式的指令一样:

#include <h-char-sequence> new-line

其中的序列与原始指令中的序列完全相同(包括原始指令中可能存在的>字符)。

该格式指令将使用" "内的特定序列标识的源文件内容替换该指令。


第三种格式:

#include pp-tokens new-line

其中include是指令名;pp-tokens是预处理标记;new-line是换行符。

#include后的预处理标记会像在普通文本中一样处理。每个当前定义为宏名的标识符会被其预处理标记的替换列表替换。完成所有替换后,结果指令应与前面两种格式中的一种匹配。< >内或者" "内预处理标记序列构成单个头文件名预处理标记的方法将由实现定义。

#define STDIO <stdio.h>
#define TEST "test.c"

#include STDIO
#include TEST

相邻字符串字面量不会串联成单个字符串字面量;因此宏展开后生成两个相邻字符串字面量的指令都是无效指令。

:串联相邻字符串字面量发生在编译阶段步骤6,即预处理后。)

#define TEST "test"".c"
#include TEST   //非法。

该格式#include指令与条件包含预处理指令结合可提高代码的可移植性。

#if defined(_WIN32) || defined(_WIN64)
  #define VERSION "windows_version.h"
#elif defined(__unix__)
  #define VERSION "unix_version.h"
#else
  #define VERSION "default_version.h"
#endif

#include VERSION

如果一个序列由一个或者多个非数字字符或者数字字符、后跟.字符和单个非数字字符构成,实现应为这样的序列提供唯一映射。头文件名的第一个字符不能是数字。实现可能忽略字母大小写的区别,并且.字符前的部分可能限制在最多8个有效字符。

使用#include预处理指令包含的源文件中可能包含#include预处理指令,其嵌套层级上限由实现定义。ISO/IEC 9899:2024标准允许#include文件最多支持不少于15层嵌套层级。


#include__has_include的比较:

1、相同点

都可以识别头文件预处理标记。

#include <locale.h>

#if __has_include(<locale.h>)
    ...
#endif

预处理标记序列所标识的头文件或者源文件的搜索方式相同。


2、差异点

#include是预处理指令。在has_include表达式中,__has_include视为一元运算符;在#ifdef#ifndef#elifdef#elifndef预处理指令以及defined宏表达式中__has_include视为定义的宏名。

#ifdef __has_include
    ...
#endif

#if defined(__has_include)
    ...
#endif 

#if __has_include("test.c")
    ...
#endif

#include用于包含头文件;has_include表达式用于检查头文件是否存在。

//检测是否存在<complex.h>头文件,但不会包含<complex.h>头文件。
#if __has_include(<complex.h>)
    //如果存在<complex.h>头文件,包含<complex.h>头文件。
    #include <complex.h>
#endif

对于#include指令,如果文件存在,正常处理;如果文件不存在,会报错。对于has_include表达式,如果文件存在,值为1;如果文件不存在,值为0,但不会报错。

编程时通常使用包含has_include表达式的预处理指令来检查特定文件是否存在;如果存在,再使用#include预处理指令包含文件,这样可以避免因文件不存在而导致的编译错误。

#if __has_include("source.c")
    #include "source.c"
    #define SOURCE 1
#elif __has_include("default.c")
    #include "default.c"
    #define SOURCE 1
    #define DEFAULT 1
#endif

#if !defined(SOURCE)
    #define SOURCE 0
#endif