【问题标题】:Included files, all or nothing?包含的文件,全部还是全部?
【发布时间】:2016-02-18 00:20:51
【问题描述】:

我写代码已经有一段时间了,但我没有受过计算机科学方面的经典训练,所以如果这个问题很荒谬,请放过我。

一段时间以来,我一直试图找到一个明确的答案,如果我在 C 中#include 一个文件,我会得到链接的文件的全部内容,还是只得到我使用的部分?如果它有 10 个函数,而我只使用其中 1 个函数,那么其他 9 个函数的代码是否包含在我的可执行文件中?这对我来说尤其重要,因为我正在研究微控制器并且内存很宝贵。

感谢您对这个问题的任何帮助。

【问题讨论】:

标签: c microcontroller microchip


【解决方案1】:

首先,头文件不会被“链接”。 #include 基本上是一个文本复制粘贴功能。包含文件中的所有内容都由预处理器粘贴到最终的翻译单元中,稍后将由编译器进行无缝处理。编译器本身对任何头文件或#include 指令一无所知。

其次,这意味着如果在你的代码中声明或定义了一些你不使用的函数或变量,它是通过#include来自头文件还是直接写在源文件中是完全无关的。完全没有区别。

第三,问题是:你的头文件中究竟包含了什么?通常,头文件不定义对象和函数,它们只是声明它们。 声明不产生任何代码,无论你是否使用函数。 声明只是告诉编译器代码(从函数定义生成)已经存在于别处。因此,只要我们谈论的是典型的头文件,#include 指令和头文件本身对最终代码大小没有影响。

第四,如果您的头文件属于某种不寻常的类型,其中包含函数(或对象)定义,请参阅上面的“firstly”和“secondly”。编译器本身一次只能看到一个翻译单元,因此编译器本身的典型策略是完全丢弃具有内部链接的未使用实体(即static对象和函数)并保留所有具有外部链接的实体。具有外部链接的实体不能被编译器正确丢弃,因为它们可能在其他翻译单元中需要。

第五,在链接阶段,链接器可以看到整个程序,因此,如果它足够先进,可以丢弃未使用的对象和函数(并且如果您允许链接器做)。同时,典型的普通链接器的包含-排除精度仅限于单个目标文件。每个目标文件对于此类链接器都是原子的。这意味着,如果您希望能够在每个函数的基础上排除未使用的函数,则可能必须采用“每个目标文件一个函数”策略,即每个 .c 文件只编写一个函数。当然,这只有在您编写自己的代码时才有可能。如果您要使用的某些第三方库不遵守此约定,那么您可能无法排除个别函数。

【讨论】:

    【解决方案2】:

    如果您#include 使用 C 语言编写文件,则该文件的全部内容将添加到您的源文件并由您的编译器编译。但是,头文件通常只有函数声明而没有定义(因此没有编译实际代码)。

    另一方面,链接器从所有库和编译的源代码中获取所有函数,并将它们合并到最终输出文件中。此时,链接器将丢弃您未使用的任何函数。

    所以,回答你的问题:只有你使用(并间接依赖)的函数才会包含在你的最终程序文件中,这与你#include 的文件无关。愉快的黑客攻击!

    【讨论】:

    • "此时,链接器将丢弃您未使用的所有函数。"其他答案说明这并不完全正确 - 更常见的是,链接器会丢弃您不使用的任何 目标文件
    • 如果是静态存档,链接器只从库中获取它需要的目标文件并将它们包含在可执行文件中。如果它是一个共享库,它会安排在运行时加载整个库,但除了可执行文件中的参考信息之外不包含任何内容。我将第二段的开头句改写为:另一方面,链接器从已编译的源代码(目标文件)中获取所有功能以及从库中获取必要的功能,并将它们合并到最终输出文件(通常是可执行文件)。
    【解决方案3】:

    你必须区分不同的场景:

    1. 包含的头文件包含什么?只声明外部函数,还是静态函数定义?
    2. 在您包含的头文件中声明的外部函数的实现如何分布?它们是全部实现在一个.c 文件中,还是分布在多个.c 文件中?

    关于第 1 点:只有通过#includeing 外部声明,其他代码都不会成为您的目标文件的一部分。而且,作为头文件一部分但未被代码引用的静态函数的定义可能不会成为目标文件的一部分——这是一种相当普遍的优化。不过,这取决于您的编译器。

    关于第2点:一些链接器只能链接整个目标文件,全部或全部。这意味着,如果在一个头文件中声明的所有外部函数都在一个 .c 文件中实现,并且如果您的代码引用了这些函数中的至少一个,那么您很可能会得到整个目标文件,包括所有其他你不使用的功能。然而,一些链接器可以避免这种情况,并在链接目标文件时删除未使用的部分。

    处理非优化链接器的一种蛮力方法是将每个外部函数放入自己的 .c 文件中。但是,您必须找到一种方法来处理其中一些函数引用一个公共静态函数的情况,该函数是原始 .c 文件的一部分...

    【讨论】:

      【解决方案4】:

      Include 只是最终向编译器呈现一个看起来像单个文件的东西(如果你在 gcc 上执行 save-temps,你会看到呈现给实际编译器的确切的单个文件)。没有比这更复杂的了。因此,如果您的 .c 文件中有一些函数原型或定义,那么让它们来自包含没有任何区别,最终结果是相同的。

      如果您包含的内容包括代码、函数而不仅仅是原型,则与您在 .c 文件本身中包含这些内容相同。这些是否出现在最终的二进制文件中与您是否将它们声明为全局或不使用静态,然后是否优化等有关。变量和结构以及其他东西也是如此。

      并非所有的链接器都是相同的,但一个常见的方法是编译器留在对象中的任何东西进入最终的二进制文件。但是,如果您将这些对象取出并用它们制作一个库,那么一些/很多?链接器不会将解决依赖关系所需部分的所有内容都吸入二进制文件中。

      您可以通过一些非常小的示例和大约 3 到 5 分钟的时间来弄清楚所有这些事情。我建议你去做。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多