gpt4 book ai didi

c++ - 无法使用GCC在Ubuntu中编译C++ —包含/库问题(collect2 : ld returned 1 exit status)

转载 作者:太空宇宙 更新时间:2023-11-03 16:50:40 25 4
gpt4 key购买 nike

我想我没有链接正确的东西?

我想调用ABC.cpp,它需要XYZ.hXYZ.cpp。所有这些都在我当前的目录中,我已经尝试过#include <XYZ.h>#include "XYZ.h"

在Ubuntu 10 Terminal上运行$ g++ -I. -l. ABC.cpp给我:

`/tmp/ccCneYzI.o: In function `ABC(double, double, unsigned long)':
ABC.cpp:(.text+0x93): undefined reference to `GetOneGaussianByBoxMuller()'
collect2: ld returned 1 exit status`

这是ABC.cpp的摘要:
#include "XYZ.h"
#include <iostream>
#include <cmath>

using namespace std;

double ABC(double X, double Y, unsigned long Z)
{
...stuff...
}

int main()
{
...cin, ABC(cin), return, cout...
}

这是XYZ.h:
#ifndef XYZ_H
#define XYZ_H

double GetOneGaussianByBoxMuller();
#endif

这是XYZ.cpp:
#include "XYZ.h"
#include <cstdlib>
#include <cmath>

// basic math functions are in std namespace but not in Visual C++ 6
//(comment's in code but I'm using GNU, not Visual C++)

#if !defined(_MSC_VER)
using namespace std;
#endif



double GetOneGaussianByBoxMuller()
{
...stuff...
}

我正在使用GNU编译器版本 g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3

这是我的第一篇文章;我希望我能提供某人需要帮助我的所有信息。我实际上已经阅读了其中一个响应中列出的“相关问题”和 Gough article,并在各处搜索错误消息。但是,我仍然不知道它如何适用于我的问题。

提前致谢!

最佳答案

当您运行g++ -I. -l. ABC.cpp时,您要求编译器从ABC.cpp中创建可执行文件。但是此文件中的代码依赖于XYZ.cpp中定义的函数,因此由于缺少该函数而无法创建可执行文件。

您有两个选择(取决于您要执行的操作)。您可以一次将所有源文件提供给编译器,以便它具有所有定义,例如

 g++ -I. -l. ABC.cpp XYZ.cpp

或者,您可以使用 -c选项编译为ABC.cpp的目标代码(在Windows中为.obj,在Linux中为.o),该代码以后可以链接,例如
 g++ -I. -l. -c ABC.cpp

它将生成 ABC.o,稍后可以将其与 XYZ.o链接以生成可执行文件。

编辑:#include和链接之间有什么区别?

完全理解这一点需要准确地理解编译C++程序时会发生什么,不幸的是,即使是许多自认为是C++程序员的人也不会。在较高的层次上,C++程序的编译经历三个阶段:预处理,编译和链接。

预处理

#开头的每一行都是预处理器指令,该指令在预处理阶段进行评估。 #include伪指令实际上是一个复制粘贴。如果您编写 #include "XYZ.h",则预处理器将该行替换为 XYZ.h的全部内容(包括 #include中的 XYZ.h递归求值)。

包含的目的是使声明可见。为了使用 GetOneGaussianByBoxMuller函数,编译器需要知道 GetOneGaussianByBoxMuller是一个函数,并且要知道它采用什么(如果有的话)参数以及它返回的值,编译器将需要查看它的声明。声明放入头文件中,并且包含头文件以使声明在使用之前对编译器可见。

编译

这是编译器运行并将您的源代码转换为机器代码的部分。请注意,机器代码与可执行代码不同。可执行文件需要有关如何将机器代码和数据加载到内存以及在必要时如何引入外部动态库的其他信息。在这里没有做。这只是您的代码从C++到原始机器指令的一部分。

与Java,Python和其他一些语言不同,C++没有“模块”的概念。取而代之的是,C++在翻译单位方面起作用。在几乎所有情况下,翻译单元都对应一个(非 header )源代码文件,例如 ABC.cppXYZ.cpp。每个翻译单元都是独立编译的(无论您是为它们运行单独的 -c命令,还是一次将它们全部交给编译器)。

编译源文件时,预处理器将首先运行,并进行 #include复制粘贴以及宏和预处理器执行的其他操作。结果是一长串C++代码,包括源文件的内容以及它包含的所有内容(以及它所包含的所有内容,等等...)。这一长串的代码是翻译单元。

编译翻译单元时,必须声明使用的每个函数和每个变量。编译器将不允许您调用没有声明的函数或使用没有声明的全局变量,因为那样一来,它将不知道所涉及的类型,参数,返回值等,并且无法生成明智的代码。这就是为什么您需要 header 的原因-请记住,此时编译器甚至没有远程意识到其他任何源文件的存在。它仅考虑通过处理 #include指令产生的这种代码流。

在由编译器生成的机器代码中,没有诸如变量名或函数名之类的东西。一切都必须成为一个内存地址。每个全局变量必须转换为存储它的内存地址,并且每个函数都必须具有一个内存地址,执行流在被调用时会跳转到该地址。对于在翻译单元中定义的内容(即,对于实现的功能),编译器可以分配地址。对于仅声明(通常是由于包含的 header 的结果)而未定义的内容,编译器此时尚不知道应使用的内存地址。这些函数和全局变量(对于编译器而言,它们仅具有声明,而没有定义/实现)称为外部符号,并且假定它们存在于不同的转换单元中。现在,它们的内存地址用占位符表示。

例如,当编译对应于 ABC.cpp的翻译单元时,它具有 ABC的定义(实现),因此它可以为函数 ABC分配地址,并且在该翻译单元中调用 ABC的任何位置,都可以为其创建跳转指令。地址。另一方面,尽管其声明是可见的,但 GetOneGaussianByBoxMuller在该翻译单元中未实现,因此其地址必须用占位符表示。

编译翻译单元的结果是一个目标文件(在Linux中带有 .o后缀)。

链接

链接器的主要工作之一是解析外部符号。也就是说,链接器浏览一组目标文件,查看其外部符号是什么,然后尝试找出应为它们分配的内存地址,以代替占位符。

在您的情况下,函数 GetOneGaussianByBoxMuller是在与 XYZ.cpp对应的转换单元中定义的,因此在 XYZ.o内部已为其分配了特定的内存地址。在与 ABC.cpp对应的翻译单元中,仅对其进行了声明,因此在 ABC.o内部,它仅是一个占位符(外部符号)。链接器(如果同时提供了 ABC.oXYZ.o)将看到 ABC.o需要为 GetOneGaussianByBoxMuller填充地址,在 XYZ.o中找到该地址,并用它替换 ABC.o中的占位符。外部符号的地址也可以在库中找到。

如果链接器找不到 GetOneGaussianByBoxMuller的地址(如您在示例中所做的那样,由于未将 ABC.o传递给编译器,则该链接器仅在 XYZ.cpp上工作),它将报告 Unresolved external symbol 错误,也称为未定义的引用。

最后,一旦编译器解决了所有外部符号,它将合并所有现在无需占位符的目标代码,添加操作系统所需的所有加载信息,并生成可执行文件。多田

请注意,通过所有这些操作,文件名无关紧要。按照惯例, XYZ.h应该包含 XYZ.cpp中定义的内容的声明,这对于可维护的代码以这种方式进行组织是很好的,但是编译器和链接器并不关心这是否成立。链接器将浏览给定的所有目标文件,并且仅浏览给定的目标文件以尝试解析符号。它既不知道也不关心符号声明所在的 header ,并且它不会尝试自动拉入其他目标文件或编译其他源文件以解决丢失的符号。

哇,那很长。

关于c++ - 无法使用GCC在Ubuntu中编译C++ —包含/库问题(collect2 : ld returned 1 exit status),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3526537/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com