虚拟析构函数将对象移出rodata部分

时间:2019-08-18 10:18:10

标签: c++ gcc elf

我有大量用constexpr构造函数构造的静态常量对象,因此它们无需任何构造函数调用即可立即存储在最终二进制文件中。

由于我正在使用内存较低的系统(STM32 MCU),所以我想减少这些对象的内存占用,并且由于它们是恒定的,因此请将它们存储在.rodata节中。编译器对此进行了顺利处理。

但是,现在我在基类中添加了一个虚拟析构函数以删除编译器警告,这些对象将存储在.data节中。

当然,我可以使用一些#pragma来专门删除基类的编译器警告并删除虚拟析构函数,但是我想知道是否有更干净的解决方案。

展示问题的极简代码:

class Object {
    int value;
public:
    constexpr Object(int param) 
    : value(param) {}

    virtual int getValue() const = 0;

    virtual ~Object() = default; // This line causes problems
};

class Derived : public Object {
    volatile int otherValue;
public:
    constexpr Derived(int param1, int param2) 
    : Object(param1), otherValue(param2) {}

    int getValue() const override { return otherValue; }
};


const Derived instance(1,2);

int main() {
    return instance.getValue();
}

此外,这是一个CompilerExplorer,用于比较是否带有虚拟析构函数:draw()

2 个答案:

答案 0 :(得分:9)

在声明虚拟方法后,便向该类添加了一个非恒定指针,该指针指向该类的虚拟表。该指针将首先被初始化为Object的虚拟表,然后在整个构造函数链中继续更改为派生类的虚拟指针。然后,它将在析构函数链中再次更改并回滚,直到指向对象的虚拟表为止。 那将意味着您的对象不再可以是纯只读对象,而必须移出.rodata。

更干净的解决方案要么是忽略类中的任何虚函数,要么是完全避免继承,并使用模板将所需的虚函数调用替换为编译时调用。

答案 1 :(得分:3)

对于具有虚拟方法的类,编译器必须为每个类定义vtable,以便根据对象所具有的类型动态调度虚拟方法调用。因此,此类的每个对象都有一个指向其类型的vtable的隐藏指针。该指针由编译器添加到类中,并且不是const,并且在整个ctor和dtor调用链中都发生了变化,因此您的instance不是const,并且不能位于{ {1}}。

一个example,展示了通过指向vtable的指针对虚拟方法的访问。

.rodata

此代码仅适用于特定的编译器。还要注意,该标准没有指定vtable的实现细节,指向它们的指针以及它们的存储位置等。