我有一个通用类,它在不同的上下文中使用 - 有时作为 静态变量,有时作为堆栈/堆上的普通变量。
当它用作普通变量时,必须调用析构函数 超出范围 - 正常。可执行文件用于嵌入式目标,其中 闪存是一种有限的资源,永远不会退出,为此我想 这个“退出”代码被禁用。
下面通过一个例子来说明这个问题。
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()
终止时会发生什么;这就是调用静态析构函数的地方 - 可以删除此代码(如果它已经存在),以便在终止时不会显式调用析构函数 - 这可能允许链接器优化然后删除未使用的代码。
您应该检查映射文件以确定析构函数是否包含在构建中,而不是查看编译器汇编器输出 - 编译器别无选择,只能生成代码,因为它不知道它是否会被外部引用。 您可能需要设置特定的链接器选项来删除未使用的代码。
一个简单的解决方案是使用 placement new - 在适当大小的静态数组上实例化对象。 您还可以使用引用变量通过实例而不是指针来访问对象。
#include <new>
alignas(alignof(A)) static char mem_for_a[sizeof(A)] ;
static A* aptr = new(mem_for_a) A ;
static A& a = *aptr ;
alignas(alignof(A)) static char mem_for_b[sizeof(A)] ;
static A* bptr = new(mem_for_b) A ;
static A& b = *bptr ;
在放置对象中,必须显式调用析构函数,以便您可以完全控制是否以及何时调用它。