C++中#include的秘密:<file> vs "file" —— 速度与优化

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:一个小细节,可能让你多等几秒编译时间

\n\n

想象一下:你正在编写一个C++项目。工作热火朝天,代码不断增长,文件目录越来越多。这时你又一次输入了#include。和往常一样,用的是尖括号?还是引号?这到底有什么区别?编译器很聪明,它自己会找到的。

\n\n

剧透:它确实能找到。但不一定总是很快。而且,不一定总能找到你想要的哪个文件。关于#include <filename> 和 #include "filename" 区别的问题,是Stack Overflow上最热门的问题之一(有数十万次浏览)。这不仅仅是语法糖的问题。这关乎编译性能、依赖关系的控制,最终关乎你代码的质量。

\n\n

在本文中,我们不仅仅列出规则。我们将深入预处理器内部,分析真实案例,并提供实用建议,帮助您加快项目构建速度。开始吧!

\n\n

预处理器是如何工作的?#include指令剖析

\n\n

在编译器看到你的代码之前,预处理器会先介入。它的任务是准备“源代码”以供编译。#include 指令是一条命令:“获取文件X的内容,并将其直接插入到这里,替换掉这一行”。

\n\n

听起来很简单。但魔鬼,一如既往,藏在细节里。具体来说,就是预处理器在哪里查找这个文件X。

\n\n

尖括号:#include <filename>

\n

当你写下 #include <iostream> 时,预处理器会专门去标准的系统路径中查找。在Linux中,这通常是 /usr/include、/usr/local/include 以及通过 -I 标志(或在Visual Studio中是 /I)传递给编译器的路径。

\n

关键点:预处理器不会查看当前项目目录。它只查看那些被指定为“系统”或“用于搜索include文件”的文件夹。

\n

这是特意设计的:为了防止意外地用一个同名的本地文件替代标准库。

\n\n

双引号:#include "filename"

\n

引号的情况更复杂,也更有趣。C++标准(第16.2节)指出:行为由实现定义。但实际上,99%的编译器(GCC、Clang、MSVC)都遵循同一个原则:

\n
    \n
  1. 首先,在包含该指令的文件所在的目录中查找(通常是当前源文件所在的文件夹)。
  2. \n
  3. 然后,按照与尖括号相同的路径进行查找(系统路径 + -I 路径)。
  4. \n
\n

也就是说,#include "myheader.h" 会首先检查你的 main.cpp 所在的文件夹。找到了,就用它。没找到,就继续按系统路径列表查找。

\n\n

这仅仅是知识,还是真正的优化?

\n

“那又怎样?”你可能会问。“电脑很快,在哪里找有什么区别?”

\n

区别是存在的,它由两个因素构成:查找速度和意图的清晰度。

\n\n

因素1:文件查找速度

\n

在小型项目(10-20个文件)中,你感觉不到区别。但在真正的企业级产品或游戏引擎中,include的数量可能超过数万个。每次你写下 #include "some_system_lib.h",预处理器都会花费额外的时间来检查当前文件夹及其所有子文件夹(取决于具体实现)。

\n

现实生活中的例子:假设你有一个包含10,000个文件的项目。其中20%的文件使用引号来包含标准库。每个这样的include都会在当前目录中产生一次额外的系统调用 stat() 或 access()。这虽然只是微秒级别,但累积到整个项目上,就是实实在在的几秒编译时间。

\n\n

使用 #include <iostream> 而不是 #include "iostream" 可以确保搜索直接从系统路径开始,跳过本地目录。这能带来

博客

书籍推荐