从文件中引用不同名称的类的方法

Refering a differently named class' method from a file

本文关键字:方法 文件 引用      更新时间:2023-10-16

我正在尝试了解c ++/g ++编译器的加载器及其使用的约定。

我有四个源文件.

你好。
你好.cpp
你好1.cpp
主.cpp

你好。

#include <iostream>
class Hello1
{
public:
int a;
void sayHello();
};

你好.cpp

 #include"Hello.h"
        void Hello1::sayHello()
        {
        std::cout<<this->a;
        }

你好1.cpp

#include"Hello.h"
void Hello1::sayHello()
{
std::cout<<"Hello";
}

主.cpp

#include"Hello.h"
    int main()
    {
    Hello1 hello;
    hello.a=5;
    hello.sayHello();
    return 0;
    }

单独预处理和组装每个文件的传递,以及

c++ -c main.cpp
也生成一个 main.o .但是当链接和加载以生成可执行文件c ++ main.o时,它会给出一个错误,指出找不到函数定义 main.o
在函数main':
main.cpp:(.text+0x19): undefined reference to Hello1::sayHello(('收集2:LD 返回 1 个退出状态
我知道如果我将类命名为 Hello 并包含相应的 Hello.cpp加载器将找到函数定义并执行成员函数。但是,如果我将头文件 Hello.h 中的类名称从 Hello 更改为 Hello1,则创建目标文件没有问题,并且编译器知道类 Hello1 存在并为其分配内存(猜测 c++ -c 命令的成功(但是加载器找不到 sayHello(( 的函数体。这似乎不是在研究 Hello.cpp 或 Hello1.cpp因为 Hello.h 除了类之外还有一个不同的类

Hello 那么即使在正常情况下,加载器如何加载函数定义呢? 它是引用文件名 Hello.h 并查找 Hello.cpp ,还是引用类名 Hello1 并查找 Hello1.cpp , 或者它是否有约束检查以查看 .h 和类名是否相同,然后只查找同名.cpp而忽略头文件中的其余类?

如果一些 c++ 大师能给我一些关于加载器在普通 c++ 文件中拾取 #include 中包含的定义的基础,那就太好了,同样在这种情况下如何通过使用不同的名称本身来引用 sayHello(( 的定义,可能吗? 或者头文件只能包含具有相同名称的类的接口

简短版本:提供一组提供符号列表的文件。(或构建系统(负责通过指定正确的文件来提供符号的"正确"列表(及其定义(。这些文件是否称为Hello,Hello1,foo或bar(+适当的后缀(并不重要

<小时 />

让我们来看看通过objdump -t -C main.o c++ -c main.cpp的结果

符号表:
00000000 l df *ABS* 00000000 主.cpp
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l O .bss 00000001 std::__ioinit
00000050 l F .text 00000042 __static_initialization_and_destruction_0(int, int(
00000092 l F .text 0000001a _GLOBAL__sub_I_main
00000000 l d .init_array 00000000 .init_array
00000000 l d .note.GNU-stack 00000000 .note.GNU-stack
00000000 l d .eh_frame 00000000 .eh_frame
00000000 l d .comment 00000000 .comment
00000000 克 F .文本 00000050 主00000000 *UND* 00000000 Hello1::sayHello((
00000000 *UND* 00000000 __stack_chk_fail
00000000 *UND* 00000000 std::ios_base::Init::Init((
00000000 *UND* 00000000 .hidden __dso_handle
00000000 *UND* 00000000 std::ios_base:::Init::~Init((
00000000 *UND* 00000000 __cxa_atexit

有一个符号main,它是一个函数,它"需要"一些在这个编译单元中没有找到的其他符号.
为了说明这一点,让我们稍微修改一下 main.cpp

#include"Hello.h"
#include <iostream>
// noinline, so that the compiler "keeps" this a function + function calls
void __attribute__ ((noinline)) foo() 
{
  std::cout << "ho ho ho" << std::endl;
}
int main()
{
  Hello1 hello;
  hello.a=5;
  foo();
  hello.sayHello();
  return 0;
}

现在输出的objdump...是

SYMBOL TABLE:
00000000 l    df *ABS*  00000000 main.cpp
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l     O .bss   00000001 std::__ioinit
00000000 l    d  .rodata    00000000 .rodata
00000084 l     F .text  00000042 __static_initialization_and_destruction_0(int, int)
000000c6 l     F .text  0000001a _GLOBAL__sub_I__Z3foov
00000000 l    d  .init_array    00000000 .init_array
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame  00000000 .eh_frame
00000000 l    d  .comment   00000000 .comment
00000000 g     F .text  0000002f foo()
00000000         *UND*  00000000 std::cout
00000000         *UND*  00000000 std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
00000000         *UND*  00000000 std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
00000000         *UND*  00000000 std::ostream::operator<<(std::ostream& (*)(std::ostream&))
0000002f g     F .text  00000055 main
00000000         *UND*  00000000 Hello1::sayHello()
00000000         *UND*  00000000 __stack_chk_fail
00000000         *UND*  00000000 std::ios_base::Init::Init()
00000000         *UND*  00000000 .hidden __dso_handle
00000000         *UND*  00000000 std::ios_base::Init::~Init()
00000000         *UND*  00000000 __cxa_atexit

如您所见,没有*UND* foo(),编译器可以自行解析该符号+调用.
好的,现在链接器有什么作用?它获取输入文件的列表,并列出这些文件中定义的所有符号。然后,它会查找依赖项并尝试解决它们。 main"需要"一个符号Hello1::sayHello()(-C 选项使它看起来像这样,请参阅 https://en.wikipedia.org/wiki/Name_mangling(.
如果链接器的符号列表中有这样的符号(并且适合(,则可以解析依赖关系。如果没有这样的符号,则会收到"未定义的引用"/"未解析的符号"错误消息.
即您必须提供一个定义所需符号的对象(文件(,否则链接器将失败。此文件的名称无关紧要。

Hello.o 提供了一个符号Hello1::sayHello(),它将满足 main.oc 中引用的要求

...
00000000 g     F .text  0000001f Hello1::sayHello()
00000000         *UND*  00000000 std::cout
00000000         *UND*  00000000 std::ostream::operator<<(int)
00000000         *UND*  00000000 std::ios_base::Init::Init()
00000000         *UND*  00000000 .hidden __dso_handle
00000000         *UND*  00000000 std::ios_base::Init::~Init()
00000000         *UND*  00000000 __cxa_atexit
..

Hello1.o 也是如此

...
00000000 g     F .text  0000001e Hello1::sayHello()
00000000         *UND*  00000000 std::cout
00000000         *UND*  00000000 std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
00000000         *UND*  00000000 std::ios_base::Init::Init()
00000000         *UND*  00000000 .hidden __dso_handle
00000000         *UND*  00000000 std::ios_base::Init::~Init()
00000000         *UND*  00000000 __cxa_atexit
...

因此,如果你调用(或让 c++/gcc 进行调用(ld [...] main.o Hello.o符号 Hello1::sayHallo(( 的定义取自 Hello.o,如果你调用 Hello1.o 的 Hello1::sayHallo(( ld [...] main.o Hello1.o 被使用.
现在调用c++ main.cpp Hello.cpp Hello1.cpp,你会得到一个"Hello.cpp:(.text+0x0(:重新定义'Hello1::sayHello(('错误,因为有两个符号具有相同的名称(并且没有机制如何解决这个问题......

您需要告诉链接器要使用哪个文件对象 (.o( 文件。 Hello.oHello1.o .所以你的命令行是这样的:

c++ main.o Hello.o

c++ main.o Hello1.o

如果您尝试同时使用两者,则会收到如下错误:

$ c++ main.o Hello1.o Hello.o
Hello.o: In function `Hello1::sayHello()':
Hello.cpp:(.text+0x0): multiple definition of `Hello1::sayHello()'
Hello1.o:Hello1.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status

在回答您的最后一个问题时,不,头文件(.h 和 .cpp 文件(的名称不需要与内部定义的类的名称匹配。

所以这是合法的:

福.H

class Bar 
{
 public:
 void someFunc();
}