在同一缓冲区中管理不同大小的类型时,如何防止内存覆盖?

时间:2016-09-15 16:35:48

标签: c++ performance memory-management dynamic-memory-allocation

目前,我有一个多态容器,可以在继承层次结构中存储任何类型。为此,我管理自己的缓冲区。

我想支持任何可以添加的未来类型,因此我不想为可以添加到容器中的类型设置静态大小。

在我的缓冲区中,由于较小的类型可能存储在较大的类型之前,因此当我不得不移除较小的元素然后移动较大的元素时会发生覆盖。

注意:对于以下示例,缓冲区中的所有类型都从相同的基类继承。

考虑这个内存布局包含一个字节对象 A (字节0)后跟一个2字节对象 B (字节2和3):

// initial state
[ a ][ b ][ b ]

// remove a
[ x ][ b ][ b ]

// x is the position to shift to, capital B is the overwritten byte
[ b ][ B ][   ]

我想要删除 A ,所以我销毁了相关对象(这意味着a现在可供我使用),然后我想移动构造 B < / strong>从a开始。显然,当我尝试写字节1时会有覆盖,因为字节1已被 B 本身占用。

我试图在分配阶段解决这个问题,通过跟踪分配的前一个元素的大小,我可以保留足够的填充字节来防止这种情况。有了这个新策略,内存布局将如下所示:

// initial state, when I added B, padding bytes = sizeof( B ) - sizeof( last ) = 1
[ a ][ - ][ b ][ b ]

// remove a, we can clearly see that there's enough space for B now
[ x ][ - ][ b ][ b ]

// safe to move B to position x
[ b ][ b ][   ][   ]

如果要添加的对象大于前一个对象,我只添加填充字节。

但是,即使在修复之后,擦除元素仍然可能导致问题发生:

// initial state: object a is 1 byte, object b is 1 byte, object c is 4 bytes
[ a ][ - ][ - ][ c ][ c ][ c ][ b ][ - ][ - ][ c ][ c ][ c ]

// erase first c, occupying bytes 1 through 5
[ a ][ b ][ - ][ - ][ c ][ c ][ c ][   ][   ][   ][   ][   ]

// erase b
[ a ][ x ][ - ][ - ][ c ][ c ][ c ][   ][   ][   ][   ][   ]

// shift c - since c > a, required padding = c - a = 4 - 1 = 3
[ a ][ - ][ - ][ - ][ x ][ c ][ c ][ c ][   ][   ][   ][   ]

// when attempting to shift C, there will be an overwrite at the capital C bytes
[ a ][ - ][ - ][ - ][ c ][ C ][ C ][   ][   ][   ][   ][   ]

这是一个产生覆盖问题的简单程序:

#include <iostream>
#include <new>
#include <cstdint>

struct Base
{
    virtual ~Base() = default;
    virtual void print() const = 0;
};

struct A : public virtual Base /* 16 byte */
{
    A( char x ) : x_{ x } {}
    virtual void print() const { std::cout << x_ << '\n'; }
    char x_;
};

struct B : public virtual Base /* 24 bytes */
{
    B( long double x ) : x_{ x } {}
    virtual void print() const{ std::cout << x_ << '\n'; }
    long double x_;
};

int main()
{
    unsigned char data[ sizeof( A ) + sizeof( B ) ];

    // these are stored as void*, used polymorphically in the actual code
    A* pa = ::new ( &data ) A{ 1 };
    B* pb = ::new ( &data + sizeof( A ) ) B{ 2 };

    pa->print();
    pb->print();

    // I want to remove the first A from my list
    pa->~A();

    // shift following element back, overwrite due to its larger size
    pb = ::new ( &data ) B{ *pb };
}

我考虑过的可能解决方案:

  • 跟踪添加的最大元素,始终使用足够的填充以确保不会发生覆盖。如果添加一个非常大的对象,这将浪费大量内存,然后会有许多小对象。
  • @ratchetfreak建议:不要移动任何东西,只需创建一个新缓冲区,并在未使用的内存变得足够大时将其全部移动到那里。
  • 当我检测到将发生覆盖时,创建一个临时缓冲区,将所有内容传输到那里,然后返回到我的普通缓冲区。 (也许我可以决定是否更好地进入这个新的缓冲区,或者将冲突的元素移回去。)

0 个答案:

没有答案