引用一个不同名称的课程'来自文件的方法

时间:2016-04-07 12:18:25

标签: c++ oop ld software-design clang++

我试图理解c ++ / g ++编译器的加载器及其使用的约定。

我有四个源文件。

Hello.h
HELLO.CPP
Hello1.cpp
main.cpp中

Hello.h

#include <iostream>

class Hello1
{
public:
int a;
void sayHello();
};

HELLO.CPP

 #include"Hello.h"

        void Hello1::sayHello()
        {
        std::cout<<this->a;
        }

Hello1.cpp

#include"Hello.h"

void Hello1::sayHello()
{
std::cout<<"Hello";
}

的main.cpp

#include"Hello.h"

    int main()
    {
    Hello1 hello;
    hello.a=5;
    hello.sayHello();
    return 0;
    }

单独预处理和汇编每个文件的传递,

c++ -c main.cpp
也会产生一个main.o。但是当链接和加载到生成可执行文件 c ++ main.o 时,它会出现错误,指出无法找到函数定义{{2}我知道如果我将类命名为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的定义()通过使用不同的名称本身,是否可能?或者头文件只能接受具有相同名称的类的接口

2 个答案:

答案 0 :(得分:3)

简短版本:提供一组提供符号列表的文件。 (或构建系统)负责提供&#34;权利&#34; 指定正确文件的符号列表(及其定义)。这些文件是否被称为Hello,Hello1,foo或bar(+适当的后缀)

并不重要

让我们通过objdump -t -C main.o

查看c++ -c main.cpp的结果
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
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 g F .text 00000050 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

有一个符号main,它是一个功能,它需要&#34;在此编译单元中未找到的其他一些符号 为了说明这一点,我们稍微修改一下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&#34;需要&#34;符号Hello1::sayHello()(-C选项使其看起来像这样,请参阅https://en.wikipedia.org/wiki/Name_mangling) 如果链接器的符号列表中有这样的符号(并且适合),则可以解析依赖关系。如果没有这样的符号,你会得到&#34;未定义的引用&#34; /&#34;未解决的符号&#34;错误信息。
即您必须提供定义所需符号的对象(文件),否则链接器将失败。这个文件的名称并不重要。

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,如果你调用ld [...] main.o Hello1.o Hello1。 o使用了Hello1 :: sayHallo() 现在打电话给c++ main.cpp Hello.cpp Hello1.cpp,你就得到一个&#34; Hello.cpp :(。text + 0x0):重新定义`Hello1 :: sayHello()&#39;错误,因为有两个具有相同名称的符号(并没有机制如何解决该问题....)。

答案 1 :(得分:2)

您需要告诉链接器使用哪个文件对象(.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文件)不需要与里面定义的类的名称相匹配。

所以这是合法的:

foo.h中

class Bar 
{
 public:
 void someFunc();
}