« 给 C 实现一个垃圾收集器 | 返回首页 | 引用计数与垃圾收集之比较 »

用 C 实现一个变长数组

我想用 C++ 的人都用过 std::vector 。它是一个可变长的数组,在很多时候都比 C 固有的定长数组要灵活。

C 里没有这样的标准化设施,但是写软件的人通常都会实现一个。正所谓,不厌其烦的重造轮子 :D 。这里我们不讨论造轮子的好坏问题,直接讨论下实现这么个东西的一点小技巧吧。总是固执于用谁做的轮子的问题,眼光就太短浅了。

一般的 vector 的实现,需要记录三个数据:数据空间的地址,空间大小,数组里已存在的元素个数。也可能是这三个元素的变体。比如在 msvc 的 stl 实现中,vector 保存的是三个 iterator:数组头指针、最后一个元素的指针、分配出来的全部空间的末尾指针。

msvc 的 vector 的分配策略是这样的。一开始不于分配空间,当第一个元素被 push back 后,空间扩展。通常,扩展的请求至少要满足请求扩展的尺寸,或者是讲已有的空间容量翻倍。比如,当你已经有 16 个单元的数组空间时,如果再申请 3 个,vector 会自己扩展到 32 个单元;若你一次要求从 16 个单元扩展到 40 个单元(超过 16*2),那么就直接扩展到 40 。

记得 stlport 也是类似的策略吧,实现的可能稍有不同。

正是如此,我们在用 vector 的时候要比较小心。如有可能,最好先 reserve 出需要的空间,而不是一个个的 push back 。否则,如果你从 0 个元素,push back 100 次,其空间会重新分配 8 次(1、2、4、8、16、32、64、128)。btw, 如果你经常这样做,最好实现一个优秀的 allocator 。IMHO ,msvc 的 stl 实现并不比 stlport 差,大多数性能差别都是因为 msvc 的 stl 实现未能给出一个通用的,优化过的分配器而已。(实现一个好的内存分配器,对于大的 C++ 软件项目,都值得去做)

今天想说的并不是怎么优化这个策略。是我这几天在写代码时发现,如果你遵照空间加倍这个策略来运作,其实并不需要三个变量来维持 vector 结构,两个就够了。那个表识容量大小的变量是多余的。:D

下面是段示范代码:

struct vector {
    int *data;
    int size;
};

void 
vector_expand(struct vector *v, int s)
{
    int new_size=v->size + s;
    if ((new_size ^ v->size) > v->size) {
       v->data=(int *)realloc(v->data,sizeof(int)*new_size * 2);
    }
}

利用二进制逻辑运算的性质,我们只需要把新的尺寸和老的尺寸异或,再和老的尺寸比较,就可以知道,新的尺寸的二进制最高是 1 的那一位的位置是否大于老的尺寸。然后分配新尺寸的两倍,空间是绰绰有余的。

当然,如果你希望空间分配的恰到好处(2^n -1 ),可以稍微写的繁琐一点,以下代码在 32bit 机器上,可以把一个数字从它的二进制为 1 的最高位到最低位都填成 1 。

unsigned 
foo(unsigned a)
{
    a|=a>>1;
    a|=a>>2;
    a|=a>>4;
    a|=a>>8;
    a|=a>>16;

    return a;
}

今天把前两天写的 gc 模块开源了,本来想放到 sourceforge 上,但是我懒的注册帐号。google code 看起来更方便,选了个 bsd 的许可。然后把代码上传了。

project 在这里 http://code.google.com/p/manualgc/ 暂无下载,请直接用 svn check out 。

开源要有勇气,尤其是这么 dirty 的代码。不过话说回来,程序写了这么多年,写代码早就不拘小节了。我感觉这个东西设计上有点意思,现在也没看到有人做类似的玩意,具体实现则是次要的东西。应该还是会对某些同学有点意义的。

暂时还没完整测试过,姑且用之,不喜欢的人请直接无视。

TrackBack

如果你想引用这篇文章,请复制下面的链接发送引用通告(GBK)
http://blog.codingnow.com/mt/mt-tb.cgi/388

Comments

个人感觉, vector 这样的东西,还是一种抽象数据类型(ADT).

用模板的语法表现起来比较自然.

对vector,能否在C中设计出类似模板的表现方式呢?

做底层 C 足够了。C++ 比较多余。用 C++ 编写代码的时间,不如好好的考虑怎样把问题划简,用跟个简洁的方式表达。

太多的可能性会影响思考。

呵呵

hello 朋友,我读过你的书,现在我也在做游戏这一块了,比你小很多,不过有些回忆还是相似的,嘿嘿

最近看云风的书,里面写的你比较倾向用C++做游戏项目的,什么时候又转C了呢?什么原因?

@Zwinger

啥隐患?没啥问题啊?你是说分配失败?分配失败后,系统就该跑不下去了。直接退出好了,不用管内存泄露。

FreeBSD 下有一个 reallocf ,不过我觉得绝大多数情况没什么用。

vector_expand()里面realloc()的使用有个内存泄露的隐患。

云风,怎么才能联系到你,有些问题想请交你

自我感觉google code比sourceforge容易上手。可能我当时比较笨的原因,sourceforge研究了一会,还不知道具体如何弄,而对于google code,当时翻看了许式伟的google code的介绍文章,一会儿就架好了一个项目。

开源是好事,自己现在水平还不行,但对gc这个话题也很感兴趣,也想写个虚拟机,慢慢消化云风的code :).

GLib上有很多类似的库,只是Windows下编译很麻烦

我也实现了一个lzw的解/压缩算法,开源在sourceforge上,发布那晚比较兴奋。你呢?呵呵。

恩,一起加班中。。。
沙发:)

Post a comment

非这个主题相关的留言请到:留言本