鉴于存在危险,为什么项目要使用-I include开关?

时间:2018-11-05 12:56:54

标签: c++ c gcc clang include-path

阅读了GCC中-I开关的详细信息后,我很震惊地发现在命令行上使用它会覆盖系统包含的内容。从preprocessor docs

  

“您可以使用-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(或叮当声,相同的区别)进行编译可以得到:

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吗?

4 个答案:

答案 0 :(得分:33)

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

在存在危险的情况下,如何使用-I管理项目?您将把include包裹在一个目录中,并使用-I添加包含该目录的目录。

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

这样,只要您不与mylib名称冲突,就不会冲突(至少就标题名称而言)。

答案 1 :(得分:19)

回头看一下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"了,它不应与其他库或系统库冲突。

答案 2 :(得分:15)

我以此为前提,认为-I危险是错误的。该语言以#include中的任何一种形式对头文件进行搜索,这些头文件的实现方式充分实现了定义,因此使用与标准头文件名称完全冲突的头文件是不安全的。只需避免这样做。

答案 3 :(得分:11)

一个明显的例子是交叉编译。 GCC受制于UNIX的历史假设,即您总是在为本地系统进行编译,或者至少是某些非常接近的东西。这就是为什么编译器的头文件位于系统根目录中的原因。缺少干净的界面。

相比之下,Windows假定没有编译器,Windows编译器也不假定您以本地系统为目标。因此,您可以安装一套编译器和一套SDK。

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

现在请注意,执行此操作时,替换标头集设计为位于基本系统的顶部上。如果标头的实现相同,则可以在替换集中省略标头。例如。 <complex.h>可能相同。复数实现的变化不大。但是,您不能随机替换<endian.h>之类的内部实现位。

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