阅读了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
吗?
答案 0 :(得分:33)
-I
超过-iquote
的原因是什么? -I
是标准化的(至少由POSIX标准化),而-iquote
没有标准化。 (实际上,我使用-I
是因为tinycc(我希望我的项目与之编译的编译器之一)不支持-iquote
。)
在存在危险的情况下,如何使用-I
管理项目?您将把include包裹在一个目录中,并使用-I添加包含该目录的目录。
includes/mylib/endian.h
-Iincludes
#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:此选项适用于知道自己在做什么的人。 “不安全”不是针对目标受众的论点。