既然存在危险,为什么项目要使用-I include开关

Why do projects use the -I include switch given the dangers?

本文关键字:include 开关 项目 存在 危险 为什么      更新时间:2023-10-16

阅读GCC中-I开关的详细说明时,我非常震惊地发现在命令行中使用它会覆盖系统包含。来自预处理器文档

"您可以使用-I覆盖系统头文件,替换您自己的版本,因为这些目录是在标准系统头文件目录之前搜索的。">

他们似乎没有撒谎。在两个不同的带有GCC 7的Ubuntu系统上,如果我创建一个文件endian.h:

#error "This endian.h shouldn't be included"

然后在同一目录中创建一个main.cpp(或main.c,相同的差异):

#include <stdlib.h>
int main() {}

然后用g++ main.cpp -I. -o main(或clang,相同的区别)编译给了我:

In file included from /usr/include/x86_64-linux-gnu/sys/types.h:194:0,
from /usr/include/stdlib.h:394,
from /usr/include/c++/7/cstdlib:75,
from /usr/include/c++/7/stdlib.h:36,
from main.cpp:1:
./endian.h:1:2: error: #error "This endian.h shouldn't be included"

所以stdlib.h包含这个types.h文件,它在第194行只显示#include <endian.h>。我明显的误解(也许还有其他人的误解)是,尖括号本可以防止这种情况发生,但——我比我想象的要坚强。

尽管不够强,因为您甚至无法通过首先在命令行上粘贴/usr/include来修复它,因为:

"如果标准系统包含目录或用-isystem指定的目录也用-I指定,则-I选项将被忽略。该目录仍在搜索中,但在系统包含链中处于正常位置的系统目录。">

实际上,g++ -v main.cpp -I/usr/include -I. -o main的详细输出将/usr/include留在列表的底部:

#include "..." search starts here:
#include <...> search starts here:
.
/usr/include/c++/7
/usr/include/x86_64-linux-gnu/c++/7
/usr/include/c++/7/backward
/usr/lib/gcc/x86_64-linux-gnu/7/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include

让我惊讶。我想这是一个问题:

考虑到这个极其严重的问题,大多数项目使用-I的正当理由是什么您可以基于偶然的名称冲突来覆盖系统上的任意标头。不是每个人都应该使用-iquote吗?

-I-iquote有什么正当理由?-I是标准化的(至少通过POSIX),而-iquote不是。(实际上,我使用-I是因为tinycc(我希望我的项目使用的编译器之一)不支持-iquote。)

考虑到危险,项目如何使用-I进行管理?将includes封装在一个目录中,然后使用-I添加包含该目录的目录。

  • 文件系统:includes/mylib/endian.h
  • 命令行:-Iincludes
  • C/C++文件:#include "mylib/endian.h" //or <mylib/endian.h>

这样,只要在mylib名称上不发生冲突,就不会发生冲突(至少就标头名称而言)。

回顾GCC手册,-iquote和其他选项似乎只是在GCC 4中添加的:https://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Directory-Options.html#Directory%20Options

因此,"-I"的使用可能是以下因素的结合:习惯、懒散、向后兼容性、对新选项的无知、与其他编译器的兼容性。

解决方案是通过将头文件放在子目录中来"命名"它们。例如,将您的endian头放在"include/mylib/endian.h"中,然后将"-Iinclude"添加到命令行,您就可以使用#include "mylib/endian.h",这不应该与其他库或系统库冲突。

我认为-I是危险的前提是错误的。该语言在搜索头文件时充分定义了#include的任何一种形式的实现,因此使用与标准头文件名称冲突的头文件是不安全的。不要这样做。

一个明显的例子是交叉编译。GCC受到UNIX历史假设的影响,即您总是为本地系统进行编译,或者至少是为非常接近的系统进行编译。这就是为什么编译器的头文件在系统根目录中。缺少干净的接口。

相比之下,Windows假定没有编译器,Windows编译器也不假定您的目标是本地系统。这就是为什么您可以安装一组编译器和一组SDK。

现在在交叉编译中,GCC的行为更像是Windows的编译器。它不再假设您打算使用本地系统标头,而是允许您准确地指定所需的标头。显然,你链接的库也是如此

现在请注意,当您执行此操作时,替换标头集被设计为位于基本系统的顶部。如果替换集中的头的实现相同,则可以省略它们。例如,<complex.h>可能是相同的。复数实现中没有太多变化。但是,不能随意替换像<endian.h>这样的内部实现位。

TL,DR:这个选项适用于那些知道自己在做什么的人。"不安全"不是针对目标受众的论点。