包含其他文件(#include
)
#include
用于将其他文件包含到当前这一行,即将此行替换为这个文件的全部内容,文件名使用尖括号<>或双引号””包裹,使用尖括号<>只会在标准库中查找,使用双引号””会优先查找源文件同级目录中的文件,若找不到则会按顺序查找包含路径中的文件,若依旧找不到文件时会回退到标准库中查找,一旦找到对应文件则停止查找。
例如在bsp.c
中使用#include "bsp.h"
,则会首先在bsp.c
所在的文件夹中进行查找,若没有找到则在包含路径中进行查找,若包含路径中有./User
并且存在./User/bsp.h
则使用该文件,若包含路径中也不存在则在标准库中查找。
文件名也可以是包含相对/绝对路径的文件名,这种情况下不会在包含路径下查找,只会按照路径关系进行查找。
设置包含路径
cmake
使用include_directories()
或target_include_directories()
添加包含路径。
Keil
在Options for Target > C/C++ > Include Path中设置,Keil会自动转换为相对路径(相对于工程文件)。
IAR
在Options > C/C++ Compiler > Preprocessor > Additional include directories中设置,IAR默认使用绝对路径,若要使用类似相对路径的效果需要使用变量$PROJ_DIR$
(此变量代表工程文件所在文件夹),例如$PROJ_DIR$\User
。
防止文件包含无限递归——头文件防护
在使用#include
时可能会出现递归地嵌套,例如两个头文件互相包含,在包含其中任意一个头文件时都会无限递归地包含这两个文件,通常使用的手段是头文件防护。类似于调用递归函数设置终止条件,头文件防护会在文件开头检查是否宏定义一个名称,若定义则终止包含,若未定义则定义该名称。
#ifndef FOO_H_INCLUDED /* 任何唯一地映射到文件名的名称 */
#define FOO_H_INCLUDED
// 文件内容在此
#endif
文件头防护使用的名称没有特别限制,STM官方通常使用__文件名大写_H
的形式,例如:
#ifndef __STM32F1XX_H
#define __STM32F1XX_H
#endif
Keil的标准库通常使用
#ifndef __math_h
#define __math_h
#endif
使用更现代的#pragma once
大部分编译器(包含常见的嵌入式编译器:AC5、AC6、IAR、GCC、更多)支持非标准语法#pragma once
,在已经包含相同文件的时候禁止处理该文件,能够实现头文件防护类似的效果,当使用现代编译器时推荐使用。若需要较高兼容性则应该使用头文件防护。
意料外的包含顺序导致定义位置错误
头文件通常包含一些结构体定义,而在包含关系复杂时可能导致结构体定义在使用结构体后,导致编译器找不到结构体定义,例如:
\\a.h
#include "b.h"
void foo(StructA* a);\\这里提示StructA未定义
\\b.h
#include "c.h"
typedef struct {
...
}StructA;
\\c.h
#include "a.h"
#include "b.h"
当我们包含b.h时会先包含c.h再包含a.h导致StructA无定义,这种情况通常在使用头文件全家桶时出现,由于包含顺序的混乱容易出现此类问题。
一个有效的解决方法是将结构体的定义提到不需要使用的头文件之前,使得按任何顺序包含头文件都能使结构体定义出现在使用之前,例如:
\\a.h
#include "b.h"
void foo(StructA* a);\\这里提示StructA未定义
\\b.h
typedef struct {
...
}StructA;
#include "c.h"
\\c.h
#include "a.h"
#include "b.h"
当然,最好是不要使用头文件全家桶,需要哪个头文件就包含哪个头文件,这样还有利于提升编译速度(当重新编译时通常只会会编译有变化的文件,若使用头文件全家桶包含到所有源文件并修改了一个头文件会导致所有源文件重新编译)。