【问题标题】:Do stdio file descriptors (stdin, stdout, stderr) get opened, simply from #include'ing <stdio.h>?是否仅通过#include'ing <stdio.h> 打开 stdio 文件描述符(stdin、stdout、stderr)?
【发布时间】:2018-05-10 14:15:56
【问题描述】:

在用 C 语言将整数转换为字符串时,我对包含 stdio.h 有点恼火——这不会用不必要的文件描述符和其他代码使二进制文件膨胀吗?还是仅当您在代码中使用标准 io 文件描述符 stdout、stdin 或 stderr 时才打开流,例如使用printfscanffprintf?如果我的代码只使用snprintf(3),也许还可以?

我拥有的最接近的现成解决方案是将整数转换为strfromd(3) 的双精度浮点数,格式字符串不会超过小数点。

bhuwansahniHow to convert integer to string in C?

中提供了一个很好的自助解决方案

我不知道如何使用(链接不起作用)itoa_itoa_fitoa_word,尽管 _fitoa_word 确实显示为带有 nm /lib64/libc6.so.6 的函数。

【问题讨论】:

  • 听起来像是 XY 问题。
  • C 的 #inculde 与 Java 的 import 不同。
  • itoa() 和朋友不是 C,而是供应商特定的扩展。
  • 用不必要的文件描述符和其他代码会不会使二进制文件膨胀? 尝试两种方法,看看大小,自己决定。

标签: c linux


【解决方案1】:

即使您不包含stdio.h,它们也会打开。它们是程序的标准流,在运行时已经打开。

程序在实际执行之前需要一些初始化。这包括加载符号表、分配内存、初始化静态数据和链接动态库等。此时打开标准流。然后控件转到程序中的main() 函数并开始执行。

对于第二个问题,标准 IO 不会使您的程序膨胀,因为您的程序几乎总是与标准 C 库 libc 链接,并且通常是动态链接(因此它不会增加您的程序的大小)可执行文件)。

是的,如果您不使用它们也没关系。你可以只使用snprintf() 之类的东西来很好地完成这一切,但标准流仍然会打开。使用与否无关紧要。

谢谢保罗·奥格尔维

【讨论】:

  • ...它们是由操作系统打开的
  • 哦对了,忘记了
  • 为什么你认为它们是由操作系统打开的,而不是外壳打开的?
  • @EricPostpischil 谁为 shell 打开流?
  • iBug:shell 可以打开自己的流。
【解决方案2】:

不,这些文件不会通过包含头文件来“打开”。包含一个头文件发生在编译时,在运行时打开文件。

所有程序都需要在调用main 函数之前完成一些初始化。这通常是由程序完成的,并不是真正从main 函数开始,而是在其他一些代码段上完成,一段由编译器提供。这段 pre-main 代码会进行一些常规初始化,以使您的程序能够正常运行,其中包括标准文件的设置。

这些文件是如何创建的,以及它们是否引用了一些较低级别的文件句柄(例如 POSIX 系统上的 stdoutFILENO_STDOUT 文件描述符)在很大程度上是无关紧要的。所有你需要知道的是,例如stdout 将在 main 函数中开始执行时存在。

【讨论】:

    【解决方案3】:

    是否包含stdio 标头与这些文件描述符是否打开无关。

    这些是从操作系统(或外壳)为您的程序打开的标准流,不是由程序自己打开的。

    【讨论】:

      【解决方案4】:

      在后台处理文件、流和格式化输出有很多工作要做。是的,包括&lt;stdio.h&gt; 并使用其中的例程会产生大量开销。但是标准流是一起运行的整个系统的一部分。 C 库提供使用流的例程,操作系统提供访问文件系统的调用,命令行 shell 或其他进程在您的进程启动时提供已经打开的流,等等。

      对于正常的进程,你可以忽略这个。对于执行命令(由您直接键入或在 shell 脚本中),它是使命令工作发生的所有事情的一小部分成本。此外,您的可执行文件可能会动态而不是静态地链接到标准库或库。这意味着该库未内置到您的可执行文件中。它在您的程序运行时可用,但它与系统中的其他进程共享,您的程序与它的连接几乎不会产生额外的开销。

      如果您正在处理某些特殊进程或其他特殊软件,例如网络守护程序或设备驱动程序,则有一些方法可以避免在标准 C 库或其部分中进行链接。但是,这些都是特定于平台的,您必须提供更多信息(可能在其他 Stack Overflow 问题中)关于您正在尝试做什么以及您正在使用哪些平台(硬件、操作系统、开发人员工具)。

      一些用于开发特殊软件的工具包括精简的库,这些库提供简单的例程而无需大量开销。

      此外,C 允许声明事物而不定义它们。 &lt;stdio.h&gt; 标头声明了它需要的各种缓冲区和其他数据结构,但是如果您不使用需要这些结构的流或其他功能,链接器可能不会从库中导入定义它们的模块,因此它们不会成为您可执行文件的一部分。其细节取决于 C 实现。

      【讨论】:

        【解决方案5】:

        我对包含 stdio.h 有点恼火——这不会用不必要的文件描述符和其他代码使二进制文件膨胀吗?

        没有。链接 C 运行时会添加到您的代码中,但没有它,您将无法做很多事情。此运行时包含将确保标准流可用的启动代码。在许多系统上,新启动的进程已经从一开始就获取了各自打开的文件,可能由父进程继承。

        【讨论】:

        • 我认为答案不是很清楚。有时我们希望构建流线型的专用流程,而不包括格式化 I/O 等花哨的功能。仅仅因为我们使用 C 并不意味着我们希望将整个库构建到我们的可执行文件中。该库应该被划分为各种模块,以便进程未使​​用的部分不会被链接。
        • @EricPostpischil 当然,但是看看这个问题,你真的会考虑这些信息在范围内吗?
        • 是的,OP 专门询问他们是否必须承担包含他们不想要的功能的成本。
        • @EricPostpischil 好吧,我的意见是明确的。这个问题表明对标准文件描述符在大多数系统上的工作方式缺乏了解,我只讨论了 C 运行时(不是标准库)是有原因的,但详细解释这一点显然是超出范围。
        【解决方案6】:

        实际上,这些流甚至在您的可执行文件被加载之前就已打开。这是通常发生的情况:

        • 某些 shell 会解析一个命令,告诉它执行您的程序。此命令可能包括stdinstdout 和`stderr 的重定向。

        • shell 调用fork() 来创建一个新进程。新进程仍在运行 shell 代码。

        • 新进程中的 shell 代码按照它解析的重定向的规定打开输入/输出流。如果你说2&gt;/dev/null,它将打开/dev/null作为文件描述符2

        • 分叉的 shell 代码调用 exec(),以将控制权交给您的代码。

        • 当您的代码写入stdout 时,它只是写入已经打开的文件描述符2

        您看,文件描述符012 的解释只是将打开的文件描述符传递给进程的约定。您可以指示您的 shell 打开更多文件描述符5&gt;myCoolStream,或关闭现有文件描述符。如果是这样,您的流程所期望的,那很好。如果您的流程需要其他东西,那是您的问题。

        因此,无论是否包含&lt;stdio.h&gt;,除了为printf() 等提供通常的函数原型和类型定义外,没有任何影响。流本身根本不受此影响。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-31
          • 1970-01-01
          • 2014-08-25
          • 2021-05-08
          相关资源
          最近更新 更多