今天遇到一个小bug,是使用protobuf的时候遇到的,浪费了两个小时。
简单描述如下:
实现升级技能书玩法:4本同样的技能书,可以合成升级为更高一级的技能书。
玩法很简单,需要判断各种条件符合后,删除其中的三本技能书,然后增加剩下的一本技能书的等级。
可是测试时发现一个奇怪的现象:有时候可能升级成功,有时候等级不变,甚至降低。

经过调试发现,原来是个非常基础的问题:指针。
我的bug的代码类似于:

SkillBook* p = pRole->GetSkillBook(bookId);
pRole->DeleteSkillBook(booksList);
p->SetLevel(p->GetLevel()+1);

也就是,先获取技能书的指针,然后删除材料技能书,然后升级技能书。
这个流程本身逻辑没问题,但是技能书是保存在protobuf的repeated成员中的,而它的底层是数组。这就导致删除后,内存整体移动了,而指针所指的数据已经改变为其它东西了。

看如下代码:

#include <iostream>

void del(int* array, int count, int idx)
{
    for(int i=idx+1; i<count; ++i)
        array[i-1] = array[i];
    array[count-1] = 0;
}

int main()
{
    int array[] = {0, 1, 2, 3, 4, 5};
    int* p = &array[2];
    std::cout << *p << std::endl;   // 2

    // delete back
    del(array, sizeof(array)/sizeof(array[0]), 3);
    std::cout << *p << std::endl;   // 2

    // delete front
    del(array, sizeof(array)/sizeof(array[0]), 1);
    std::cout << *p << std::endl;   // 4

    return 0;
}

由此可见,对于数组这类的连续内存来说,删除指针所指结点的后续结点,不会影响指针的值,但删除前置结点,则会改变指针的值。
这本是非常基础的问题,但这次花了两个小时查这个bug,原于对protobuf底层的不熟悉,如果protobuf的repeated成员是链表,则不会出现类似的问题。
由此,我也想到了stl的vector,也有同样需要注意的地方。