【问题标题】:Why does the C++ Compiler not find the function definitions? [closed]为什么 C++ 编译器找不到函数定义? [关闭]
【发布时间】:2017-08-30 21:10:20
【问题描述】:

我编写了一些代码,试图了解编译和链接之间的关系 - 并查看函数声明在源文件中的何处以及是否必须重复。

我写的两个文件是:

Person.cpp:

#include <string>  
class Person {
  public:
   Person(std::string name) {
   (*this).name = name;
   };
   std::string getName() {
   return name;
   };
   std::string name;
};

和文件

PersonMain.cpp:

#include <iostream>
class Person {
  public:
    Person(std::string);
    std::string getName();
    std::string name;
 };
int main(){
          Person* charlie = new Person("Charlie");
          std::cout << "Hi, my name is " << charlie->getName();
          }

我在 PersonMain.cpp 中重复了 Class Person 和 Class Person 函数声明(不是定义)。

我现在使用 gcc C++ 编译器编译和链接这两个文件:

g++ *.cpp -o runthis.exe

然后我收到以下错误消息:

PersonMain.cpp:(.text+0xfe): 未定义的引用 Person::Person(std::basic_string<char, std::char_traits<char>,std::allocator<char> >)' PersonMain.cpp:(.text+0x13c): undefined reference to Person::getName()' collect2.exe: error: ld 返回 1 个退出状态

链接时似乎找不到 Person 类方法。这是为什么?我怎样才能治愈它?

附录:

我在 PersonMain.cpp 中明确地重复了 Person 的声明,但并没有像通常那样将其重新声明到头文件中。所以我已经在这里完成了预处理器步骤。 This faq 建议:

[预处理器] 一次处理一个 C++ 源文件,方法是替换 包含相应文件内容的指令(通常只是声明)[...]

及以后:

[链接器]通过替换引用来链接所有目标文件 具有正确地址的未定义符号。这些符号中的每一个 可以在其他目标文件或库中定义。

我添加此注释是因为@engf-010 表示即使其他编译单元中需要它,也不会编译具有该编译单元中实际未使用的代码的编译单元。 enf-010建议我把定义和声明放到头文件中,但是faq文章说只有声明应该放在那里,定义可以放在别处。

【问题讨论】:

  • 请阅读const
  • 你有两个名为 Person 的类。
  • 在这种情况下你会得到一个重新定义错误。
  • @LogicStuff 但我只是在 PersonMain 中重新声明而不是重新定义 Person。重复声明没问题,我想。定义在 Person 中,这是链接器应该寻找它的地方。通常,所有的重新声明都在一个头文件中,但我明确地把它们放在里面。
  • 呃,请至少正确格式化这个混乱。

标签: c++ gcc linker


【解决方案1】:

通常将声明和定义分别拆分为.h.cpp 文件。

您的代码将如下所示:

person.h - 声明:

#include <string>

class Person {
public:
        Person(std::string n);
        std::string getName() const;

        std::string name;
};

person.cpp - 实现:

#include <string>
#include "person.h"

Person::Person(const char* n) : name(n) { }
std::string Person::getName () const { return name; }

personmain.cpp - 你在哪里使用你的班级Person

#include <iostream>
#include "person.h"

int main()
{
        Person* charlie = new Person("Charlie");
        std::cout << "Hi, my name is " << charlie->getName ();
}

就像@engf 提到的那样,本质上,您在代码中得到的是Person 的双重定义,并且没有实现。你不能轻易地在主文件中定义Person 并在其他地方(你不应该)在另一个翻译单元(即person.cpp)中实现Person::Person(),而不让编译器知道你实现了什么。您不能像这样转发声明 Person 并在 personmain.cpp 中使用它:

class Person;
...
Person* charlie = new Person("whatever");

因为编译器应该知道完整的类型才能发出构造Person-object 的代码。 Person 不能有两个定义。如果没有定义,您就无法实现Person 的成员,这些定义可供代码的所有用户访问。所以,你留下的就是上面这样的方案。

为了详细说明编译和链接,非常粗略地说,当编译器看到对未在同一翻译单元中本地定义的符号的引用时,它会将适当的记录放入目标文件中。当这个目标文件被传递给链接器(可能连同一堆其他目标文件和库)时,链接器应该将这些记录解析为输入目标文件或库中某处的符号(函数、变量等)。如果它成功了,它将所有引用的代码组合成一个图像。在您的情况下,链接器无法为构造函数解析引用符号 Person::Person。可能this 是不错的问答开始。

【讨论】:

  • 我想我可能对“声明”、“定义”和“实施”的使用感到困惑。你在 person.h 中所做的对我来说就像一个声明,而不是一个定义。定义不就是实施吗?
  • 对这种混乱感到抱歉。你说得对。在这种情况下,定义 = 实现和声明 = 接口。这是我的错(现在有点困了:)要纠正这个。
  • 我现在知道我一直在做错什么:我认为 Person.cpp 中的“class Person{}”内容是为了显示它所在的命名空间。但是使用该 class 关键字会向编译器发出信号做一个新课。我认为这不会发生,因为这里没有给出定义。您通过敲击类 Person{} 并改用 Person:: 为我指明了正确的方向。
  • Person.cpp 中的 "class Person{}" 东西是用来显示它在哪个命名空间中的 - 不可能。你不能告诉编译器像这样使用类的命名空间。您必须在此处使用:: - 范围解析运算符。但无论如何,你不会以这种方式成功地安排事情,因为编译器不知道你在person.cpp 中实现了什么。因此,您必须使用单一文件,或者您必须将接口/实现拆分为 .h.cpp 文件。
【解决方案2】:

你得到的是一个源文件中的定义和另一个源文件中的声明。 编译包含定义的源文件时,编译器得出的结论是没有任何“真实”代码要生成,因为您没有使用该类。 所以你最终得到一个空的目标文件。

您可以在源文件中定义一个类,但如果您不在该文件中使用该类,则该类将被丢弃为未使用。 如果您在其他源文件中使用该类(通过它的声明),编译器会为成员访问生成代码,但在链接期间找不到这些代码。

【讨论】:

  • 这很有趣!好的,我知道编译器会在同一编译单元中未使用的编译单元中丢弃代码。我如何告诉编译器这段代码将在不同的编译单元中使用? (这就是我在这里尝试做的)
  • @yippy_yay:你不能把这些告诉编译器。通常做的是:将您的类定义放在一个头文件中,并将该头文件包含在需要它的源文件中。请记住,一个类可以被定义多次(即使在同一个源中)而不会在链接期间导致重新定义错误。只要这些定义相同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-20
  • 2018-06-16
  • 1970-01-01
  • 2016-08-12
  • 1970-01-01
  • 2011-11-27
相关资源
最近更新 更多