这个C++堆栈分配器有哪些改进空间?

7
有关基于堆栈的分配器,您有什么建议吗? (除了使用具有私有/公共成员的类的建议)
struct Heap
{
    void* heap_start;
    void* heap_end;
    size_t max_end;

    Heap(size_t size)
    {
        heap_start = malloc(size);
        heap_end = heap_start;
        max_end = size + (size_t) heap_start;
    }

    ~Heap()
    {
        ::free(heap_start);
    }

    void* allocate(size_t bytes)
    {

        size_t new_end = ((size_t) heap_end) + bytes;

        if( new_end > max_end )
            throw std::bad_alloc();

        void* output = heap_end;
        heap_end = (void*) new_end;
        return output;
    }

}

我的C++内存池怎么样? - paxdiablo
只是想知道是否有任何优化的方法,或更好的约定等。 - Unknown
好的,修正标题以使其更清晰。 - paxdiablo
这是一个线性分配器,如果您不关心释放或可以一次性释放所有内容,则非常完美。我不会将其描述为堆。 - Tom Leys
@Tom 根据在线定义(http://cplus.about.com/od/glossar1/g/heap.htm),堆是用于分配动态变量的任何内存块。 - Unknown
4个回答

4
size_t new_end = ((size_t) heap_end) + bytes;

不好的写法,永远不要这样做。你假设sizeof(size_t) == sizeof(void*),并且如果 bytes ==(size_t)(-1) 这将不起作用。

此外,您需要确保返回的指针是对齐的。否则,你会遇到问题。因此,根据你的平台,你需要确保字节数是4或8的倍数。

class {...
char *max_end,*head_end,*heap_start;
};

...
max_end=heap_start+size;
...
bytes=align_to_platform_specific_value(bytes);
if(max_end-heap_end >= bytes) {
   void* output = (void*)heap_end;
   heap_end+=bytes;
   return output;
}
throw std::bad_alloc();

建议?不要重复造轮子。有很多好的连接池库可供使用。

但是我没有使用char的原因是因为char不能保证是1个字节。据我所知,size_t始终是void,因为类型可能跨越整个地址空间。此外,我的gcc编译器不允许我进行void算术运算。但你说得对,关于对齐的问题:这是一种时空权衡。 - Unknown
1
标准明确定义了sizeof(char)=1,虽然在实践中sizeof(size_t)==sizeof(void*)很常见,但标准并没有定义它。但是,标准确实定义了sizeof(intptr_t)==sizeof(void*)(但像VC++这样的一些编译器不支持intptr_t)。 - Artyom
在5.3.3 Sizeof [expr.sizeof]中,您可以找到"sizeof(char),sizeof(signed char)和sizeof(unsigned char)都是1。对于任何其他基本类型应用sizeof的结果是实现定义的"。 - Francesco
@Francesco,我在谈论的是某些注定要失败的编译器上sizeof(size_t) != sizeof(void*)。是的,我知道根据标准sizeof([[un]signed] char) == 1 - Artyom
1
@Artyom 我知道你知道 :) 但是未知用户的第一条评论指出“char不能保证为1个字节”,这是不正确的。所以我想支持你的评论,而不是与之争论! - Francesco
显示剩余2条评论

4
您已经实现了基于堆栈的内存分配器。如果没有留下空隙,您将无法释放内存。通常,池是指具有固定大小插槽的连续内存块,这些插槽是双向链接的,以允许常数时间添加和删除。 这里有一个可以用作指南的示例。它与您的思路相同,但包括对已分配节点的基本迭代器,并使用模板来进行类型识别。

啊,是的,那似乎是正确的术语。我以为它被称为内存池。 - Unknown

2

有两个明显的问题:

1/ 您没有deallocate()

2/ 如果您当前的策略不是总是按照分配的确切相反顺序进行释放,则编写deallocate()将非常困难。您需要考虑客户端想要在已使用部分中释放内存的情况。

当然,如果您按相反顺序释放内存,则(2)就不是问题了。如果根本不释放内存,则(1)也不是问题。

这取决于您希望它执行什么操作。


作为一个通常的内存池,你只需要分配一堆对象,然后在一个函数调用中释放它们。但是我完全忘记了Sharptooth所说的,如果他们不是POD类型,我可能需要调用每个对象的析构函数。 - Unknown
只有在它们拥有不属于池的文件句柄/其他对象时,您才需要调用析构函数。如果所有内容都只分配内存,并且所有内存都在池中,则您不一定需要调用析构函数。 - Tom Leys

1

你的堆不允许释放。在C++中,你如何使用它来处理使用new分配的对象?


你提出了一个很好的观点,我想我只考虑了包含POD。 - Unknown
即使使用POD,您仍需注意operator delete。如果不这样做,将使用默认的operator delete,这可能导致程序崩溃。 - sharptooth
你有什么建议?我需要重载全局删除运算符吗? - Unknown
如果您只针对有限的一组类使用堆,最好为它们重载operator delete。 - sharptooth
你可以很容易地使用此类分配器与放置 new。 - justinhj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接