什么是nm告诉我关于在Linux上使用gcc编译的程序中的vtable?

时间:2017-08-23 09:04:54

标签: c++ inheritance gcc vtable nm

我将Base类更改为抽象(即我将其中一个方法设为纯虚拟)并重新编译它。当我将它与派生类链接时,链接器抱怨了vtable。我用nm调查了一些事情,但我不确定nm告诉我的是什么。我只是通过删除* .o文件并重新编译Derived类来解决问题,但我想了解这里的vtable究竟发生了什么。

我的原始代码是:

Base.h

class Base {
    public:
       virtual void doSomething();
};

Base.cpp

#include <iostream>
#include <Base.h>
void Base::doSomething() {
    std::cout << "Base::doSomething()" << "\n";
}

Derived.h

#include <Base.h>
class Derived : public Base {
    public:
        Derived();
        void doSomething() override;
};

Derived.cpp

#include <iostream>
#include <Derived.h>
Derived::Derived() {
    std::cout << "Derived::Derived() constructor" << "\n";
}
void Derived::doSomething() {
    std::cout << "Derived::doSomething()" << "\n";
}

Makefile包含以下内容:

CXXFLAGS = -std=c++14 -pedantic -Wall -Werror -I ./

default: build
clean:
    rm -f proggy
    rm -f *.o

proggy: proggy.o Base.o Derived.o 
    g++ -o proggy proggy.o Base.o Derived.o

Base.o: Base.cpp Base.h
Derived.o: Derived.cpp Derived.h

然后我跑了make,一切都很顺利。为了记录,我在这一点上也运行了nm,如下所示:

$ nm -C Derived.o | grep Base
00000000 W Base::Base()
00000000 W Base::Base()
00000000 n Base::Base()
         U typeinfo for Base
         U vtable for Base

我看到 typeinfo for Base 未定义,但这一切看起来都很开心。

此外,在Base.o中,现阶段还有这个:

nm -C Base.o | grep Base
000000d4 t _GLOBAL__sub_I__ZN4Base11doSomethingEv
00000000 T Base::doSomething()
00000044 R typeinfo for Base
0000004c R typeinfo name for Base
00000038 R vtable for Base

然后我按如下方式更改了Base.h和Base.cpp以使类抽象:

Base.h

class Base {
    public:
       virtual void doSomething() = 0; // pure virtual
};

Base.cpp

#include <iostream>
#include <Base.h>
// void Base::doSomething() {
//     std::cout << "Base::doSomething()" << "\n";
// }

然后当我跑步时,我收到了这个错误:

$ make proggy
g++ -std=c++14 -pedantic -Wall -Werror -I ./   -c -o Base.o Base.cpp
g++ -o proggy proggy.o Base.o Derived.o
Derived.o:(.rodata+0x5c): undefined reference to `typeinfo for Base'
Derived.o: In function `Base::Base()':
Derived.cpp:(.text._ZN4BaseC2Ev[_ZN4BaseC5Ev]+0x48): undefined reference to `vtable for Base'
collect2: error: ld returned 1 exit status
Makefile:17: recipe for target 'proggy' failed
make: *** [proggy] Error 1

其中c ++ filt告诉我以下内容:

$ c++filt _ZN4BaseC2Ev
Base::Base()

然后我按照以下方式运行nm,它告诉我:

$ nm -C Derived.o | grep Base
00000000 W Base::Base()
00000000 W Base::Base()
00000000 n Base::Base()
         U typeinfo for Base
         U vtable for Base

我可以看到 typeinfo for Base 是未定义的,但它也是一开始的,所以我认为这不是一个问题,但确实如此。另请注意,现在没有在Base.o中提及Base。

$ nm -C Base.o | grep Base  

即。这个nm和grep命令找不到任何东西。

最后,我删除了所有* .o文件并再次运行make,一切都很好。然后我运行nm来查看nm报告,并报告以下内容:

$ nm -C Derived.o | grep Base
00000000 W Base::Base()
00000000 W Base::Base()
00000000 n Base::Base()
00000000 V typeinfo for Base
00000000 V typeinfo name for Base
00000000 V vtable for Base

这一切意味着什么?

我的问题是:

  1. nm告诉我这里有什么,以及之前出现了什么问题 虚表?
  2. 它没有得到什么需要它?
  3. 为什么重新编译派生类修复了事情以及这是做什么的 修理vtable?

2 个答案:

答案 0 :(得分:6)

从日志中

$ make proggy
g++ -std=c++14 -pedantic -Wall -Werror -I ./   -c -o Base.o Base.cpp
g++ -o proggy proggy.o Base.o Derived.o

这意味着您的makefile不会重新构建Derived.o,只会重新构建Base.o。 这当然会引发这个问题。

您需要修正Makefile,以便为Derive.cpp Base.h添加适当的依赖关系。

答案 1 :(得分:3)

vtable存储已实现的虚拟方法的地址。如果类的所有方法都是纯虚拟的并且没有实现,那么就不需要生成vtable *,因为没有办法自己实例化这样的类(在调试模式下,仍然可以生成vtable,指向所有陷阱功能)。

当您使用Derived.cpp编译Base.h具有非纯虚函数时,它会引用Base的vtable。

当您随后将Base.h更改为仅包含纯虚函数并重建Base.o时,来自Base.o的vtable消失。此时您需要重建Derived.o,否则它将继续引用不存在的vtable。

重建Derived.o时,编译器会发现Base是纯虚拟类,并在Derived.o本身为它生成vtable,因为它知道Base.o中没有{ {1}}。

在基类中重新排序虚拟函数后,会出现另一个潜在问题。然后派生类,如果不重建,最终可能会在其父类中调用错误的函数。

这就是为什么重要的是让依赖链正确,以确保在必要时重建依赖对象文件。

Derived.o: Derived.cpp Derived.h Base.h

*血腥细节依赖于编译器,但GCC的做法是:由于无法实例化纯虚拟类,因此vtable生成实际上是推迟,直到至少有一个实现,因为只有这样才能有一个类的实例。因此,vtable是使用每个派生实现生成的,并导出为“弱”对象(类型V),以允许在链接时合并潜在的重复项。