为什么项目使用-I include开关给出危险?

在GCC中读取-I开关的精细打印,我很震惊地发现在命令行上使用它会覆盖系统包含。 来自预处理器的文档

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

他们似乎并不撒谎。 在两个不同的endian.h系统上使用GCC 7,如果我创建一个文件endian.h

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

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

 #include  int main() {} 

然后使用g++ main.cpp -I. -o main编译g++ main.cpp -I. -o 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 。 我明显的误解(也许是其他人的误解)是尖括号会阻止这一点,但是 – 我比我想象的更强。

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

“如果标准系统包含目录或使用-isystem指定的目录也使用-I指定,则忽略-I选项。仍然搜索目录,但作为系统目录在系统包含链中的正常位置。 “

实际上, g++ -v main.cpp -I/usr/include -I. -o main的详细输出g++ -v main.cpp -I/usr/include -I. -o main g++ -v main.cpp -I/usr/include -I. -o main leaves / 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给出危险? 您将包含在目录中并使用-I添加包含该目录的目录。

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

有了它,只要你不在mylib名称上发生冲突,你就不会发生冲突(至少与头名相关)。

回顾GCC手册,看起来像-iquote和其他选项只在GCC 4中添加: https : -iquote

所以使用"-I"可能是一些组合:习惯,懒惰,向后兼容,无知新选项,与其他编译器的兼容性。

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

我这是你的前提, -I认为这是危险的。 该语言将搜索头文件,其格式为#include足以实现定义,使用与标准头文件名完全冲突的头文件是不安全的。 简单地说不要这样做。

一个明显的例子是交叉编译。 GCC在历史上的UNIX假设中遇到了一些问题,即你总是在编译本地系统,或者至少是非常接近的东西。 这就是编译器的头文件在系统根目录中的原因。 缺少干净的界面。

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

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

现在请注意,当您执行此操作时,替换标头集旨在置于基本系统之上 。 如果它们的实现相同,您可以在替换集中省略标题。 例如, 是相同的。 复数实现没有那么多变化。 但是,您不能随机替换类的内部实现位。

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