【标题党】记一个关于Redis-4.0.1版本下zslGetElementByRank函数的诡异问题

引子

人在桌前坐,bug天上来。昨天早上到了小组,正准备总结一下爬山之旅,东哥就给我发了一个bug,让我也帮忙瞅瞅。。。

bug描述

是一个使用Redis跳跃表的demo,可以参照
东哥在RedisDB上的求助贴
东哥在StackOverFlow上的提问

这个关于Redis的demo如下

zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
    zskiplistNode *x;
    unsigned long traversed = 0;
    int i;
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
        {
            traversed += x->level[i].span;
            x = x->level[i].forward;
        }
        if (traversed == rank) {
            return x;
        }
    }
    return NULL;
}

int main(int argc, char **argv) {

    zskiplistNode *node;

    zskiplist *zsl = zslCreate();           //create a skiplist

    /*插入操作*/
    zslInsert(zsl, 65.5, sdsnew("tom"));  //insert some data
    zslInsert(zsl, 87.5, sdsnew("jack"));
    zslInsert(zsl, 70.0, sdsnew("alice"));
    zslInsert(zsl, 95.0, sdsnew("tony"));

    node = zslGetElementByRank(zsl, 4);       //get element by rank

    printf("%s->%f\n", node->ele, node->score); //这里直接coredump了
    return 0;
}

抽象出的核心代码如下

//在File func.c
char *func(char *array)
{   char *x;
    x = array[n]//这里的意思 n < N 并没有任何数组越界等问题
    return x;
}
// 在File main.c
int main(void)
{
    char *array = (char *)malloc(sizeof(char) * N);
    char *node = func(array);
}

而现在的问题是x的值和node的值并不相同,并且经过我在Linux上的多次实验,发现x和node的低32位相同!
而东哥则表示,若不调用zslGetElementByRank函数,而是直接展开(就是相当于手动inline)则没有问题,同时对于跳跃表的其他函数使用都很正常。。。。

更加诡异的debug

本着debug全靠灵感的思路,我选择将Redis的内存分配器由默认的jemalloc换成了libc的ptmalloc,重新编译之。

$ make MALLOC=libc

结果居然一下子就好了。。。

于是就开始纠结为什么会这样。。。

娄神的最终击杀

最后本着试试看的心态请教了娄神,在结队debug的过程中发现了问题,在make时出现了这样的警告
这里写图片描述

额。。。。
所以。。。

最终的原因

由于main函数所在的server.c并没有zslGetElementByRank的函数声明,所以编译器认为其返回的是int类型,node和x只有低32位作为一个int类型是正确的。

而用到的其他函数,像zslFirstInRange则已经被声明好,所以可以正确运行。

但是为什么我们将jemalloc换成libc的ptmalloc就好了呢?

经过早上的实验,发现在我的Linux上,对于小对象,ptmalloc返回的地址很低,比如像0x1306010这样的一个地址,而jemalloc返回的地址却很高,像0x7f3c6e215000这样的。而对于大对象,两种malloc都会返回一个大地址。

所以,由于我们的疏忽,导致只有低32位的地址正确,所以ptmalloc这样的低地址就可以跑出正确结果,而jemalloc的高地址就因为前面的高32位丢失而直接coredump了。

后记

这个bug的解决让我一下子就想起了《Effective C++》中的Item 53:不要忽视编译器的警告。 即使是很熟悉的代码,一但有warning,也要仔细看看。在学习的路上,始终都要像那个最开始抱着VC++6.0的我一样,不忽视每个warning。

猜你喜欢

转载自blog.csdn.net/xiyoulinux_kangyijie/article/details/78518911