考虑到危险,为啥项目使用 -I include 开关?
Posted
技术标签:
【中文标题】考虑到危险,为啥项目使用 -I include 开关?【英文标题】:Why do projects use the -I include switch given the dangers?考虑到危险,为什么项目使用 -I include 开关? 【发布时间】:2019-04-08 20:13:08 【问题描述】:阅读 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
(或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>
。我明显的误解(也许是其他人的误解)是尖括号可以防止这种情况发生,但是 -I 比我想象的要强大。
虽然不够强足够,因为你甚至无法通过在命令行中粘贴 /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
吗?
【问题讨论】:
不是缺陷。按设计。有时(特别是对于大型项目)开发人员需要屏蔽一个或几个系统文件或可能的标准头文件。 @HostileFork 如果把修改留在评论里,可能看不到。 @Eljay 这仍然是一个缺陷。众所周知,处理依赖关系(通常是多文件编译)的 C 和 C++ 系统存在巨大缺陷。这就是为什么人们多年来一直试图用现代模块系统取代它。 @KonradRudolph • 包含和依赖项的整体设计是历史遗留问题。按照今天的预期,我同意……有缺陷。-I
是历史遗产的一部分,设计时内存和驱动器空间与今天的硬件相比是微不足道的。对于 C++20,我希望模块、契约和概念 lite 都能成为标准。模块将改变 C++ 软件的开发方式,但其向后兼容性将持续很长时间。
你可以用锋利的刀切开你的手腕然后死去。那么为什么厨师仍然使用锋利的刀具来准备食物呢? -I
覆盖系统包含,因为有时您需要 覆盖它们。没有什么要补充的了。
【参考方案1】:
-I
超过-iquote
有什么正当理由? -I
是标准化的(至少是 POSIX),而 -iquote
不是。 (实际上,我使用的是-I
,因为 tinycc(我希望我的项目编译的编译器之一)不支持-iquote
。)
考虑到危险,如何使用-I
管理项目?您将包含包含在一个目录中并使用 -I 添加包含该目录的目录。
includes/mylib/endian.h
命令行:-Iincludes
C/C++ 文件:#include "mylib/endian.h" //or <mylib/endian.h>
这样,只要您不与 mylib
名称发生冲突,就不会发生冲突(至少就标题名称而言)。
【讨论】:
我也有一个项目希望能够使用 TCC 构建,因此感谢您提及这一点。虽然 -iquote 可能不在 POSIX 中,但自 2005 年发布的 v4 以来它一直在 gcc 中(我认为所有版本的 clang 都支持它)......所以如果有人的项目依赖于 13 年前可用的命令行设置,他们可能会考虑 -iquote。【参考方案2】:回顾 GCC 手册,它看起来像 -iquote
和其他选项仅在 GCC 4 中添加:https://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Directory-Options.html#Directory%20Options
所以"-I"
的使用可能是以下因素的某种组合:习惯、懒惰、向后兼容性、对新选项的无知、与其他编译器的兼容性。
解决方案是通过将头文件放在子目录中来“命名空间”您的头文件。例如,将您的字节序头放在"include/mylib/endian.h"
中,然后将"-Iinclude"
添加到命令行,您可以#include "mylib/endian.h"
这不应该与其他库或系统库冲突。
【讨论】:
为什么在你的例子中使用-I
?是因为习惯、懒惰……还是真的需要?
@anatolyg 因为如果您不使用“-I
”,那么命名标题的要求就不那么重要了
好的,我现在明白逻辑了。之所以需要它,是因为一些“真实”的原因,比如兼容性。【参考方案3】:
一个明显的例子是交叉编译。 GCC 受到了 UNIX 历史假设的影响,即您总是为本地系统进行编译,或者至少是非常接近的东西。这就是编译器的头文件位于系统根目录中的原因。缺少干净的界面。
相比之下,Windows 假定没有编译器,Windows 编译器也不假定您的目标是本地系统。这就是为什么您可以安装一组编译器和一组 SDK。
现在在交叉编译中,GCC 的行为更像是 Windows 的编译器。它不再假定您打算使用本地系统标头,而是让您准确指定所需的标头。显然,您链接的库也是如此。
现在请注意,当您执行此操作时,替换标头集被设计为位于基本系统的顶部。如果它们的实现相同,您可以在替换集中省略标题。例如。 <complex.h>
很可能是相同的。复数实现没有太多变化。但是,您不能随意替换 <endian.h>
之类的内部实现位。
TL,DR :此选项适用于知道自己在做什么的人。 “不安全”不是目标受众的论据。
【讨论】:
在交叉编译时,您通常会调用一个不同的编译器,该编译器使用与 /usr/include 不同的目录,因此 -I 并不比在单个实例中更需要。 如果一个项目控制了链接的标准库的版本,它通常也应该控制标准头文件的位置,以确保包含的头文件中的任何定义与预期的匹配由标准库提供。【参考方案4】:你的前提是-I
很危险,这是错误的。该语言使用#include
的任一形式搜索头文件,充分实现定义,使用与标准头文件名称冲突的头文件是不安全的。干脆不要这样做。
【讨论】:
它超越了与"standard"头文件的冲突;看起来它可以是任何内部实现文件(例如/usr/include/x86_64-linux-gnu/sys/
目录中的那些文件...),这似乎几乎可以是任何名称。 debugreg.h
?您怎么知道要避免所有可能的此类名称?
sys/*
中的标头不是“内部”。它们是由 POSIX 或特定操作系统定义的扩展,您不需要关心用 -I
屏蔽它们,除非您的应用程序需要使用它们。无论如何,将标题命名为 sys/anything 似乎是一个非常糟糕的主意。
你怎么知道?你学习标准的。或者您使用了解它们的分析工具。您可以打开所有错误报告,因此如果存在命名冲突,它会破坏构建,直到您修复它。多种方式。
@Baldrickk sys/
是 POSIX 使用的众所周知的标头命名空间。在任何情况下,c99 -xc -E - -v </dev/null
都会为您提供系统头文件搜索路径。如果某些东西是组件的第一级子目录,那么将其用作项目的标头命名空间可能是个坏主意(c99 -E -v -xc - </dev/null 2>&1 | awk '/#include </,/End of/ print $0 '|sed -n 's/^ //p'|while read d; do printf '%s\n' $d/*/; done |sed 's|/$||; s|.*/||' |sort -u
应该给你这些)。
@PSkocik 我不建议您污染命名空间,只是说有办法确保您不会与其他任何东西发生冲突/潜在冲突。以上是关于考虑到危险,为啥项目使用 -I include 开关?的主要内容,如果未能解决你的问题,请参考以下文章