C / C ++标题和实现文件:它们如何工作?

时间:2012-02-10 08:01:50

标签: c++ compilation header-files

这可能是一个愚蠢的问题,但我现在已经搜索了很长一段时间,并且在网上找不到明确的答案(做了我的尽职调查)。

所以我是编程新手......我的问题是,主函数如何知道不同文件中的函数定义(实现)?

离。说我有3个文件

  • 的main.cpp
  • myfunction.cpp
  • myfunction.hpp

//main.cpp

#include "myfunction.hpp"
int main() {
  int A = myfunction( 12 );
  ...
}

-

//myfunction.cpp

#include "myfunction.hpp"
int myfunction( int x ) {
  return x * x;
}

-

//myfunction.hpp

int myfunction( int x );

-

我知道预处理器如何包含头代码,但是头和主函数如何知道函数定义是否存在,更不用说它了?

如果不清楚或者我对这个问题非常错误,我很抱歉,这里有新的

7 个答案:

答案 0 :(得分:60)

头文件声明函数/类 - 即告诉编译器何时编译.cpp文件可用的函数/类。

.cpp文件定义了这些函数 - 即编译器编译代码,因此生成实际的机器代码来执行在相应的.hpp文件中声明的那些操作。

在您的示例中,main.cpp包含.hpp个文件。预处理器将#include替换为.hpp文件的内容。此文件告诉编译器函数myfunction在其他地方定义,它需要一个参数(int)并返回int

因此,当您将main.cpp编译为目标文件(.o扩展名)时,它会在该文件中记下它需要函数myfunction。将myfunction.cpp编译为目标文件时,目标文件中有一个注释,其中包含myfunction的定义。

然后,当您将两个目标文件链接到一个可执行文件时,链接器会将最终结束 - 即main.o使用myfunction中定义的myfunction.o

我希望有帮助

答案 1 :(得分:15)

从用户的角度来看,您必须了解编译是一个两步操作。


第一步:对象编译

在此步骤中,您的* .c文件单独编译为单独的目标文件。这意味着当编译 main.cpp 时,它对 myfunction.cpp 一无所知。他唯一知道的是你声明具有此签名的函数:int myfunction( int x )存在于另一个目标文件中。

编译器将保留此调用的引用并将其直接包含在目标文件中。对象文件将包含"我必须使用一个int 调用 myfunction ,它将返回给我一个int 。它会保留所有extern次来电的索引,以便之后可以与其他人进行关联。


第二步:链接

在此步骤中,linker将查看目标文件的所有索引,并尝试解决这些文件中的依赖项。如果不在那里,你将从中获得着名的undefined symbol XXX。然后,他将这些引用转换为结果文件中的实际内存地址:二进制文件或库。


然后,您可以开始询问如何使用像Office套件这样的巨大程序来实现这一目标,这些程序有很多方法和方法。对象?好吧,他们使用shared library机制。你知道他们的' .dll'和/或' .so'您在Unix / Windows工作站上的文件。它允许推迟解决未定义的符号,直到程序运行。

它甚至允许使用dl*函数按需解决未定义的符号

答案 2 :(得分:5)

1。原则

当你写:

int A = myfunction(12);

这被翻译为:

int A = @call(myfunction, 12);

其中@call可以看作是字典查找。如果你考虑字典类比,你可以在知道它的定义之前知道一个单词( smogashboard ?)。您只需要在运行时将定义放在字典中。

2。 ABI上的一点

@call 如何运作?因为ABI。 ABI是一种描述许多内容的方法,其中包括如何执行对给定函数的调用(取决于其参数)。调用契约很简单:它只是说明了每个函数参数的位置(一些将在处理器的寄存器中,另一些在堆栈中)。

因此,@ call实际上是:

@push 12, reg0
@invoke myfunction

函数定义知道它的第一个参数( x )位于reg0

3。但我虽然字典是动态语言的?

你是对的,在某种程度上。动态语言通常使用哈希表来实现,该哈希表用于动态填充的符号查找。

对于C ++,编译器会将翻译单元(粗略地说,预处理的源文件)转换为对象(通常为.o.obj)。每个对象都包含一个它引用的符号表,但其定义未知:

.undefined
[0]: myfunction

然后链接器将对象组合在一起并重新对齐符号。此时有两种符号:

  • 库中的那些,可以通过偏移引用(最终地址仍然未知)
  • 那些在库之外的地址,其地址在运行时才完全未知。

两者都可以以同样的方式对待。

.dynamic
[0]: myfunction at <undefined-address>

然后代码将引用查找条目:

@invoke .dynamic[0]

当加载库(例如DLL_Open)时,运行时最终会知道 符号在内存中的映射,并使用实际地址覆盖<undefined-address> (对于这次运行)。

答案 3 :(得分:4)

正如Matthieu M.的评论中所建议的那样,链接器作业可以在正确的位置找到正确的“功能”。编译步骤大致如下:

  1. 为每个cpp文件调用编译器并将其转换为 目标文件(二进制代码)与符号表关联 函数名称(名称在c ++中被修改)到它们中的位置 对象文件。
  2. 链接器只调用一次:每个目标文件都在 参数。它将从一个对象解析函数调用位置 通过符号表将文件归档到另一个文件。一个main()函数必须 存在于某个地方。最终生成二进制可执行文件 当链接器找到它需要的一切时。

答案 4 :(得分:4)

预处理器将头文件的内容包含在cpp文件中(cpp文件称为翻译单元)。 编译代码时,将分别检查每个转换单元的语义和语法错误。不考虑翻译单元中函数定义的存在。 .obj文件是在编译后生成的。

在obj文件链接的下一步中。函数的定义(类的成员函数)被搜索并发生链接。如果未找到该函数,则抛出链接器错误。

在您的示例中,如果函数未在myfunction.cpp中定义,则编译仍会继续进行,没有问题。链接步骤中将报告错误。

答案 5 :(得分:2)

int myfunction(int);是函数原型。您使用它声明函数,以便编译器在您编写myfunction(0);时知道您正在调用此函数。

标题和主要功能如何知道函数定义是否存在?
嗯,这是Linker的工作。

答案 6 :(得分:1)

编译程序时,预处理器会将每个头文件的源代码添加到包含它的文件中。编译器编译每个 .cpp文件。结果是一些.obj个文件 之后是链接器。链接器从您的主文件开始获取所有.obj文件。每当它找到没有定义的引用(例如变量,函数或类)时,它会尝试在创建的其他.obj文件中找到相应的定义在编译阶段或在链接阶段开始时提供给链接器 现在回答您的问题:每个.cpp文件都编译成包含机器代码中的指令的.obj文件。当您包含.hpp文件并使用在另一个.cpp文件中定义的某个函数时,在链接阶段,链接器会在相应的.obj文件中查找该函数定义。这就是它找到它的方式。