考虑到危险,为啥项目使用 -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 &lt;endian.h&gt;。我明显的误解(也许是其他人的误解)是尖括号可以防止这种情况发生,但是 -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 &lt;mylib/endian.h&gt;

这样,只要您不与 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 的编译器。它不再假定您打算使用本地系统标头,而是让您准确指定所需的标头。显然,您链接的库也是如此。

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

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 &lt;/dev/null 都会为您提供系统头文件搜索路径。如果某些东西是组件的第一级子目录,那么将其用作项目的标头命名空间可能是个坏主意(c99 -E -v -xc - &lt;/dev/null 2&gt;&amp;1 | awk '/#include &lt;/,/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 开关?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的包裹被归类为危险包裹

为啥 iframe 被认为是危险的和安全风险?

(为啥)FSCTL_SET_OBJECT_ID 危险吗?

为啥在actor中创建actor是危险的

端口随意开很危险 常见端口解析

cpu使用率多少正常 超过多少危险