我想确切地了解程序编译器的哪个部分以及链接器所关注的内容。所以我写了下面的代码:
#include <iostream>
using namespace std;
#include <string>
class Test {
private:
int i;
public:
Test(int val) {i=val ;}
void DefinedCorrectFunction(int val);
void DefinedIncorrectFunction(int val);
void NonDefinedFunction(int val);
template <class paramType>
void FunctionTemplate (paramType val) { i = val }
};
void Test::DefinedCorrectFunction(int val)
{
i = val;
}
void Test::DefinedIncorrectFunction(int val)
{
i = val
}
void main()
{
Test testObject(1);
//testObject.NonDefinedFunction(2);
//testObject.FunctionTemplate<int>(2);
}
我有三个功能:
FunctionTemplate - 功能模板。
现在如果我编译这段代码,我会在DefinedIncorrectFunction中找到缺少';'的编译器错误。
假设我修复此问题然后注释掉testObject.NonDefinedFunction(2)。现在我收到链接器错误。
现在注释掉testObject.FunctionTemplate(2)。现在我收到了缺少';'的编译器错误。
对于函数模板,我理解编译器不会触及它们,除非在代码中调用它们。所以缺少';'在我调用testObject.FunctionTemplate(2)之前,编译器没有抱怨。
对于testObject.NonDefinedFunction(2),编译器没有抱怨,但链接器没有。据我所知,所有编译器都关心是知道这是一个声明的NonDefinedFunction函数。它并不关心实施。然后链接器抱怨,因为它无法找到实现。到目前为止一切都很好。
我感到困惑的是当编译器抱怨DefinedIncorrectFunction时。它没有寻找NonDefinedFunction的实现,但是它经历了DefinedIncorrectFunction。
所以我不清楚编译器究竟做了什么以及链接器做了什么。我的理解是链接器链接组件与他们的调用。因此,当调用NonDefinedFunction时,它会查找NonDefinedFunction的编译实现并抱怨。但编译器并不关心NonDefinedFunction的实现,但是它确实用于DefinedIncorrectFunction。
如果有人可以解释或提供一些参考,我真的很感激。
谢谢。
答案 0 :(得分:5)
编译器的功能是编译您编写的代码并将其转换为目标文件。因此,如果您错过了;
或使用了未定义的变量,编译器会抱怨,因为这些是语法错误。
如果编译没有任何障碍,则会生成object files。目标文件具有复杂的结构,但基本上包含五件事
编译器编译代码并使用遇到的每个符号填充符号表。符号指的是变量和函数。 This question的答案解释了符号表。
它包含链接器可以处理到工作应用程序或共享库中的可执行代码和数据的集合。目标文件中有一个称为符号表的数据结构,它将目标文件中的不同项映射到链接器可以理解的名称。
要注意的要点
如果从代码中调用函数,则编译器不会将代码调用 目标文件中例程的最终地址。相反,它提出了一个 占位符值放入代码中并添加一个告诉链接器的注释 从所有的各种符号表中查找引用 它处理的对象文件并在那里粘贴最终位置。
生成的目标文件由链接器处理,链接器将填充符号表中的空白,将一个模块链接到另一个模块,最后提供可由加载器加载的可执行代码。
所以在你的具体案例中 -
undefined reference to NonDefinedFunction
错误中止,因为它无法找到对相关符号表条目的引用。进一步理解它可以说你的代码结构如下
File- try.h
#include<string>
#include<iostream>
class Test {
private:
int i;
public:
Test(int val) {i=val ;}
void DefinedCorrectFunction(int val);
void DefinedIncorrectFunction(int val);
void NonDefinedFunction(int val);
template <class paramType>
void FunctionTemplate (paramType val) { i = val; }
};
文件try.cpp
#include "try.h"
void Test::DefinedCorrectFunction(int val)
{
i = val;
}
void Test::DefinedIncorrectFunction(int val)
{
i = val;
}
int main()
{
Test testObject(1);
testObject.NonDefinedFunction(2);
//testObject.FunctionTemplate<int>(2);
return 0;
}
让我们首先只复制并汇编代码但不链接
$g++ -c try.cpp -o try.o
$
此步骤没有任何问题。所以你在try.o中有目标代码。让我们尝试将其链接起来
$g++ try.o
try.o: In function `main':
try.cpp:(.text+0x52): undefined reference to `Test::NonDefinedFunction(int)'
collect2: ld returned 1 exit status
您忘了定义Test :: NonDefinedFunction。让我们在单独的文件中定义它。
File- try1.cpp
#include "try.h"
void Test::NonDefinedFunction(int val)
{
i = val;
}
让我们将其编译成目标代码
$ g++ -c try1.cpp -o try1.o
$
再次成功。让我们尝试仅链接此文件
$ g++ try1.o
/usr/lib/gcc/x86_64-redhat-linux/4.4.5/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: ld returned 1 exit status
没有主要赢得了't link !!
现在您有两个单独的对象代码,其中包含您需要的所有组件。只需将它们中的两个传递给链接器,然后让它完成剩下的工作
$ g++ try.o try1.o
$
没有错误!!这是因为链接器找到所有函数的定义(即使它分散在不同的目标文件中)并使用适当的值填充目标代码中的空白
答案 1 :(得分:4)
我相信这是你的问题:
我感到困惑的是当编译器抱怨DefinedIncorrectFunction时。它没有寻找NonDefinedFunction的实现,但是它经历了DefinedIncorrectFunction。
编译器试图解析DefinedIncorrectFunction
(因为您在此源文件中提供了定义)并且存在语法错误(缺少分号)。另一方面,编译器从未看到NonDefinedFunction
的定义,因为此模块中根本没有代码。您可能在另一个源文件中提供了NonDefinedFunction
的定义,但编译器并不知道。编译器一次只查看一个源文件(及其包含的头文件)。
答案 2 :(得分:4)
假设你想吃点汤,所以你去餐馆。
您在菜单中搜索汤。如果您没有在菜单中找到它,请离开餐厅。 (有点像编译器抱怨它无法找到功能)如果你找到它,你会怎么做?
你打电话给服务员给你点汤。然而,仅仅因为它在菜单中,并不意味着它们也在厨房里。可能是一个过时的菜单,可能是有人忘了告诉厨师他应该做汤。再说一次,你离开了。 (就像链接器中找不到符号的错误一样)答案 3 :(得分:2)
编译器检查源代码是否符合语言并遵守语言的语义。编译器的输出是目标代码。
链接器将不同的对象模块链接在一起以形成exe。函数的定义位于此阶段,并且在此阶段添加了调用它们的相应代码。
编译器以translation units的形式编译代码。它将编译源.cpp
文件中包含的所有代码,
DefinedIncorrectFunction()
在源文件中定义,因此编译器会检查它的语言有效性
NonDefinedFunction()
在源文件中有任何定义,因此编译器不需要编译它,如果定义存在于某个其他源文件中,该函数将被编译为该转换单元的一部分,并进一步链接将链接到它,如果在链接阶段链接器找不到定义,那么它将引发链接错误。
答案 4 :(得分:2)
编译器的作用以及链接器的作用取决于 实现:合法实现可以只存储标记化的 源于“编译器”,并在链接器中执行所有操作。 现代实现做越来越多地推迟到链接器 更好的优化。很多模板的早期实现都没有 甚至查看模板代码直到链接时间,而不是匹配大括号 足以知道模板的结束位置。从用户的角度来看, 你对这个错误是否需要一个更感兴趣 诊断“(可以由编译器或链接器发出) 或者是未定义的行为。
对于DefinedIncorrectFunction
,您提供了源文本
哪个实现需要解析。该文本包含一个
需要诊断的错误。如果是
NonDefinedFunction
:如果使用该功能,则无法提供
完整的定义(或提供多个定义)
程序违反了一个定义规则,该规则未定义
行为。不需要诊断(但我无法想象
没有提供缺失定义的实现
已使用的功能)。
在实践中,只需通过检查即可轻松检测出错误 单个翻译单元的文本输入由标准定义 “需要诊断”,并将被检测到 编译器。通过检查无法检测到的错误 单个翻译单元(例如,缺少定义,可能是 目前在不同的翻译单位)正式未定义 行为 - 在很多情况下,链接器可以检测到错误, 在这种情况下,实现实际上会发出错误。
在内联函数等情况下,这有些修改
允许在每个翻译单元中重复定义,并且极其重复
由模板修改,因为直到很多错误都无法检测到
实例。在模板的情况下,标准离开
实现了很大的自由:至少,编译器必须
解析模板足以确定模板的结束位置。该
然而,标准添加了诸如typename
之类的东西,以允许更多
在实例化之前解析。但是,在依赖的情境中,有些人
在实例化之前不可能检测到错误,这可能需要
放置在编译时或链接时 - 早期实现
有利的链接时间实例化;编译时实例化占主导地位
今天,VC ++和g ++使用它。
答案 5 :(得分:1)
缺少的分号是语法错误,因此代码不应该编译。即使在模板实现中也可能发生这种情况。从本质上讲,有一个解析阶段,虽然对人类来说很明显如何“修复和恢复”编译器不必这样做。它不能只是“假设分号存在,因为这就是你的意思”并继续。
链接器查找要在需要的地方调用的函数定义。这里不需要,所以没有投诉。这个文件中没有错误,因为即使它是必需的,它也可能不会在这个特定的编译单元中实现。链接器负责收集不同的编译单元,即“链接”它们。
答案 6 :(得分:1)
啊,但你可以在另一个编译单元中使用NonDefinedFunction(int)。
编译器为链接器生成一些输出,基本上说明以下内容(其中包括):
答案 7 :(得分:1)
链接器用于链接外部模块中定义的代码(可能) - 您将与此特定源文件一起使用的库或目标文件,以生成完整的可执行文件。因此,如果您有一个声明但没有定义,您的代码将编译,因为编译器知道链接器可能在其他地方找到丢失的代码并使其工作。因此,在这种情况下,您将从链接器而不是编译器中获得错误。
另一方面,如果代码中存在语法错误,编译器甚至无法编译,您将在此阶段收到错误。宏和模板的行为可能有点不同,如果不使用它们就不会导致错误(模板与具有更好界面的宏一样多),但它也取决于错误的重力。如果你陷入困境,以至于编译器无法弄清楚模板化/宏代码结束的地方和常规代码的启动,它将无法编译。
使用常规代码,编译器必须编译甚至死代码(源文件中未引用的代码),因为有人可能希望通过将.o文件链接到他的代码来使用来自其他源文件的代码。因此,非模板化/宏代码必须在语法上正确,即使它不直接在同一源文件中使用。