C++禁用静态变量的析构函数

C++ disable destructors for static variables

本文关键字:析构函数 变量 静态 C++      更新时间:2023-10-16

我有一个通用类,用于不同的上下文 - 有时为静态变量,有时作为堆栈/堆上的普通变量。

当它用作普通变量时,析构函数必须在它消失时调用超出范围 - 正常。可执行文件用于嵌入式目标,其中闪存是一种有限的资源,永远不会退出,为此我想要此"退出"代码将被禁用。

下面是一个示例来说明该问题。 A是其中析构函数在正常情况下是必需的,但在静态情况下不需要变量。

struct Abstract {
  virtual ~Abstract() {}
};
struct A : public Abstract {
  int i = 0;
};
static A a;
static A b;

以下是生成的汇编代码(使用 -Os -std=c++11 -fno-exceptions -fno-rtti 编译)生成者:http://goo.gl/FWcmlu

Abstract::~Abstract():
    ret
A::~A():
    ret
A::~A():
    jmp operator delete(void*)
Abstract::~Abstract():
    jmp operator delete(void*)
    pushq   %rax
    movl    $__dso_handle, %edx
    movl    a, %esi
    movl    A::~A(), %edi
    call    __cxa_atexit
    popq    %rcx
    movl    $__dso_handle, %edx
    movl    b, %esi
    movl    A::~A(), %edi
    jmp __cxa_atexit
vtable for Abstract:
vtable for A:
b:
    .quad   vtable for A+16
    .long   0
    .zero   4
a:
    .quad   vtable for A+16
    .long   0
    .zero   4

如上面的汇编代码所示,相当数量的指令被发出给执行此清理代码。

有什么可以禁用这个不需要的清理代码的吗?它不需要移植 - 只要它在最新版本的 GCC 中工作。属性、链接器脚本、更改对象文件和其他技巧大多是受欢迎的。

答案是通过创建一个包装器:

template<class T>
class StaticWrapper
{
public:
    using pointer = typename std::add_pointer<T>::type;
    template<class... Args>
    StaticWrapper(Args && ...args)
    {
        new (mData) T(std::forward<Args>(args)...);
    }
    pointer operator ->()
    {
        return reinterpret_cast<pointer>(mData);
    }
private:
    alignas(T) int8_t mData[sizeof(T)];
};

此包装器可用于包装不应调用析构函数的类:

struct A
{
    int i;
};
static StaticWrapper<A> a;
a->i = 1;

它的工作方式是 - 我们(静态地)保留一些足够大的内存来包含正确对齐的对象。然后,我们使用就地 new 运算符在保留内存中创建实际对象,并将潜在的参数转发到其构造函数。我们可以使用运算符 -> 从包装器访问对象。析构函数永远不会被调用,因为从编译器的角度来看,任何地方都没有类 T 的对象 - 只有一个字节数组。我们只是使用这些字节来保存对象。

只需使用对堆分配变量的引用。 它会泄漏,但我想这就是你想要的。

static A& a = *(new A);

在裸机嵌入式系统中,您通常可以访问运行时启动代码(通常在汇编程序中);此代码执行全局静态初始化,包括在调用main()之前调用构造函数。 它还确定如果main()终止会发生什么;这就是调用静态析构函数的地方 - 可以删除此代码(如果它已经存在),以便在终止时不会显式调用析构函数 - 这可能允许链接器优化然后删除未使用的代码。

您应该检查映射文件以确定析构函数是否包含在构建中,而不是查看编译器汇编器输出 - 编译器别无选择,只能生成代码,因为它不知道它是否会被外部引用。 可能需要设置特定的链接器选项来删除未使用的代码。

一个简单的解决方案是使用 place new - 在适当大小的静态数组上实例化对象。 您还可以使用引用变量通过实例而不是指针访问对象。

#include <new>
static char mem_for_a[sizeof(A)] ;
static A* aptr = new(mem_for_a) A ;
static A& a = *aptr ;
static char mem_for_b[sizeof(A)] ;
static A* bptr = new(mem_for_b) A ;
static A& b = *bptr ;

在放置对象中,必须显式调用析构函数,以便完全控制是否调用析构函数以及何时调用析构函数。