【问题标题】:What exactly happens when a header file is included?包含头文件时究竟会发生什么?
【发布时间】:2014-08-20 10:53:53
【问题描述】:

我有两个疑问:

  1. 头文件实际上包含什么?所有函数定义还是只有原型声明?
  2. 包含头文件时会发生什么?头文件的所有内容是否都附加到我的代码中?还是将头文件的具体(或全部)内容加载到内存中,并根据我的代码调用函数?

【问题讨论】:

  • 它被复制粘贴在您编写#include 行的确切位置。
  • "头文件实际上包含什么?" -- 试试看。
  • @JimBalter:您对如何查找和查看它有什么建议吗?如何做到这一点并不是很明显。
  • 网络上有数以百万计的 C 头文件,只需单击几下即可,大多数关于该语言的教科书中也有。谷歌搜索“示例 C 头文件”会带来大量信息。如果您有任何希望或计划成为一名程序员,我敦促您发展基本基本智力技能。就 SO 提出问题并不能弥补他们的不足。
  • 这是来自上述谷歌搜索的一个有用结果:web.stanford.edu/~fringer/teaching/operating_systems_03/…

标签: c c-preprocessor


【解决方案1】:

头文件实际上包含什么?所有的函数定义还是只有原型声明?

头文件包含函数声明、外部变量、宏、结构等。最佳做法是将函数定义保留在.c 文件中。

包含头文件时会发生什么?头文件的所有内容是否都附加到我的代码中?还是将头文件的具体(或全部)内容加载到内存中,并根据我的代码调用函数?

头文件只不过是在你使用#include的地方插入它们的内容而已。如果你愿意,你可以自己写。

包含头文件相当于复制头文件的内容,但我们不这样做,因为它很容易出错,并且在源文件中复制头文件的内容不是一个好主意,特别是如果我们有多个源文件组成我们的程序。

编辑:

对于程序完整执行的内存布局,您可以点击此链接http://fgiasson.com/articles/memorylayout.txt

【讨论】:

  • 好的。但是 .c 文件在哪里呢?编译器如何知道这一点并使用该函数?函数定义是如何带入内存执行的?
  • "那么 .c 文件在哪里?" -- 在你的磁盘某处。 “编译器怎么知道”——因为你告诉它源文件的名称。 “并使用函数”——编译器不使用函数,它们生成调用函数的代码。 “函数定义是如何带入内存执行的?” -- 函数定义不会被带入内存,也不会被执行...... 代码会被带入内存,包括编译器为实现您的函数而生成的所有代码。
【解决方案2】:

只是一个旁注,它可能很明显......

  • ad 2/ 如果你#include 一个文件,includig 中的#include 指令 文件被包含的文件替换。这就是发生的事情。这是 预处理器的作用。按定义

  • ad 1/ 头文件可以继续任何事情(编译器可以 接受)。其他答案描述了用于 在 c 和 h 文件之间组织代码,但这些只是约定。 他们非常好坚持,你很可能会迷失方向 更大的项目,如果你不这样做。

实际上,如果你为一个非常简单的微控制器编写 c,你可以完全不用头文件及其包含。

如果您是学生/爱好者/有时间,单独运行预处理器并查看中间输出可能会很有用。 有趣的事实:很久以前我曾经使用 c 预处理器来生成自定义字母。

无论如何,我的观点是:c preporocesor 所做的事情是由标准定义的,而在文件之间组织代码是您/您的团队可以在一定程度上选择的约定。这种区别很重要。 p>

【讨论】:

    【解决方案3】:
    1. 头文件应该包含有用的函数声明(可能还有变量)和类型定义(structunionenumtypedef)和宏(类函数或类对象宏) .这些通常声明某些库的某些部分提供的设施。例如,标准头文件,例如<stdio.h>,描述了标准库的一部分。现代头文件可能包含inline 函数定义,但除此之外,头文件既不应该定义函数也不应该定义变量——它应该只声明它们。

      还要注意,公共标头通常应该只声明对源代码使用者有用的类型和函数。它应该只包括其他提供使用头文件中的设施所必需的声明的头文件;它不应该(通常)包含无关的标头,这些标头仅在实现标头所描述的设施时才需要。

    2. 头文件的内容(以及它包含的任何头文件)被复制到翻译单元 (TU) 中。注意到#define 宏和条件处理(#if 等)。您通常可以找到一种方法来查看给定源文件上预处理器的输出,以查看 C 编译器实际看到的内容(但要小心;它可能非常冗长)。例如,选项通常是-E(POSIX 要求c99 命令)或-P(较旧的常用选项)。

    TU 是编译器正确看到的。它包含您文件中的源代码以及您包含的标头中的相关信息。

    【讨论】:

    • (1) 应该包含宏和常量。
    【解决方案4】:

    C 和 C++ 是语言,它们广泛使用称为函数转发的功能。这意味着,例如,您可以说:

    void f(int i); /* note the semicolon */
    

    这意味着:“我保证,在源代码后面的某个地方,有人会定义函数 f 的实际作用”。在这样forward之后,你可以使用这个函数,因为它确实存在,编译器稍后会要求有人实际定义这个函数(如果没有定义,编译将失败)。这个forward也叫header,因为它实际上是函数定义的header:

    void f(int i) /* Header */
    /* Body */
    {   
        /* ... */
    }
    

    头文件是一个文件,主要包含这样的转发。您可以使用头文件来访问在其他地方定义的函数(例如在不同的编译单元或外部库中),然后附加所需的目标文件或库以提供这些头文件的实现。除了函数转发之外,在头文件中您还可以找到结构定义、常量或其他项目,这些都是正确使用已定义函数所必需的。

    编译器如何将您的转发与 .c 文件中的实际实现相匹配?很简单 - 标题。它试图找到一个函数定义(实现),它与你之前声明的头文件完全匹配。

    如果您使用#include 头文件会发生什么,编译器(特别是预处理器)会将头文件的全部内容复制到您放置#include 的位置。这就是魔法,没有更多的事情发生。

    在运行时,头文件根本无关紧要,因为您的可执行文件只包含可执行代码。编译器要么加载库中所有可用的函数(由头文件访问),要么(很可能,如果打开优化)只选择您在代码中实际使用的这些函数。

    有趣的是,只有当有人实际使用该函数时,编译器才需要函数定义(实现)。否则,转发被忽略。试试:

    void f(int i);
    
    int main(int argc, char ** argv)
    {
        /* Do not use f here */
    
        return 0;
    }
    

    【讨论】:

    • “头文件是一个文件,主要包含这样的转发。”——嗯,不。除了函数原型之外,许多(如果不是大多数)头文件还包含结构、typedef、宏、常量等。
    • "在运行期间,头文件根本不重要,因为您的可执行文件只包含可执行代码。" -- 标头可以包括可执行代码,尤其是。内联函数。说头文件在运行时无关紧要就像说源文件在运行时无关紧要……这在技术上是正确的,但毫无意义。
    • @JimBalter 从问题中我得到了一个印象(可能是错误的),OP 认为,头文件在运行时被加载到内存中——这就是我的答案。
    • @JimBalter “除了函数转发之外,在头文件中您还可以找到结构定义、常量或其他项目,这些都是正确使用已定义函数所必需的。”。当我再想一想时,仅由常量和结构组成的标头在您提供可以使用它们的函数之前并不是很有用。宏是一个不同的故事。我用 C++ 编写,但根据我的经验,大多数头文件主要包含函数/类,以及 - 作为补充 - 结构、常量、宏等。
    • "再想一想,在你提供一个可以使用它们的函数之前,仅由常量和结构组成的标头并不是很有用。" ——你应该尝试更多地思考。一些头文件的唯一目的是定义特定的数据类型(结构)。这是 C 而不是面向对象的,对该数据类型进行操作的函数很可能不是该数据类型的“成员”,因此它们出现在其他头文件中。但我的观点是,您的回答过于关注原型,而 C 标头的使用范围要广泛得多。
    猜你喜欢
    • 2012-08-18
    • 2015-12-25
    • 1970-01-01
    • 2015-10-23
    • 2011-01-18
    • 1970-01-01
    • 2012-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多