C++ 健全性检查失败:几个 variables/memory 位置被更改为垃圾,即使我从未访问过它们

C++ sanity check fails: several variables/memory positions are changed to garbage, even if I never acess them

我正在实施一个跳过列表。它是什么并不重要,但它现在适用于 1000 个节点,但不适用于 10000 个节点。我得到了没有意义的 SegFaults,所以我打印了一些变量。令我惊讶的是,很多不应该改变的东西变成了垃圾值。例如,我在函数 insertNode 之前和之后打印了 inputValue。它有时会重置为零,而此时应该始终递增。看代码(跳过读取文件输入,问题发生在while循环):

int main(int argc, char** argv) {
    string filename = "";

    if( argc == 2 )
      filename = argv[1];
    else
        return 0;

    list = new skiplist();

    fstream inputFile(filename.c_str(), ios_base::in);

    inputFile >> numberofnodes;
    inputFile >> list->minimumKey;
    inputFile >> list->maximumKey;

    printf("%d\n", numberofnodes);
    printf("%d\n", list->minimumKey);
    printf("%d\n", list->maximumKey);

    list->Maxlevel = 1;

    list->header = new node();
    list->tail = new node();
    list->header->key = list->minimumKey;
    list->tail->key = list->maximumKey;


    for ( int i=1; i<=MAXIMUMLEVEL; i++ ) {
       list->header->forward[i] = list->tail;
       list->tail->forward[i] = NULL;
    }

    int sanityCheck = 134153;
    // insert nodes
    int inputKey;
    int inputValue = 0;
    int * keys = new int[numberofnodes];
    while (inputFile >> inputKey)
    {
        inputValue++;
        keys[inputValue] = inputKey;
        insertNode(inputKey, inputValue);  
        if(sanityCheck != 134153)       // dark magic changes this value
            keys[9999999999999999999999]++;  // program crashes here
                                             // it would otherwise crash on while
    }
    printf("\n\nNodes inserted: %d\n\n",inputValue);

我运行 Valgrind。无效内存 writes/read 发生在变量发生变化之后,至少我相信是这样。这就是我添加完整性检查的原因。正如我所想,在尝试访问密钥 [9999999999999999999999] 之前没有无效内存 writes/read。但是那一行只能 运行 更改 int sanitycheck,我从来没有这样做过。

最后,这是 insertNode 的代码。我在上面看不到任何可能导致此问题的内容:

void insertNode(int newKey, int newValue){
    node * update[MAXIMUMLEVEL];
    node * auxNode = list->header;
    for(int i=list->Maxlevel; i >=1; i--) {
        while ( auxNode->forward[i]->key < newKey ) {
            auxNode = auxNode->forward[i];
        }
        update[i] = auxNode;
    }
    auxNode = auxNode->forward[1];
    if ( auxNode->key == newKey ) {
        auxNode->value = newValue;
    } else {
        int randomLevel = 1;
        while ( (rand() / double(RAND_MAX)) < LEVELPROBABILITY && randomLevel < MAXIMUMLEVEL ) {
            randomLevel++;
        }

        if ( randomLevel > list->Maxlevel ) {
            for ( int i = list->Maxlevel+1; i <= randomLevel; i++ ) {
                update[i] = list->header;
            }
            list->Maxlevel = randomLevel;
        }
        node * newNode = new node();
        newNode->key = newKey;
        newNode->value = newValue;
        for ( int i=1; i<=MAXIMUMLEVEL; i++ ) {
            newNode->forward[i] = NULL;
        }

        for ( int i=1; i<=list->Maxlevel; i++ ) {
            newNode->forward[i] = update[i]->forward[i];
            update[i]->forward[i] = newNode;
        }
    }
}

以及结构:

typedef struct node {
    int key;
    int value;
    node * forward[MAXIMUMLEVEL+1];
}node;

struct skiplist {
    int minimumKey;
    int maximumKey;
    int Maxlevel;
    node * header;
    node * tail;
};

EDIT:
#define MAXIMUMLEVEL 16 
#define LEVELPROBABILITY 0.5

我什至没有使用 mallocs。有指针操作,但 valgrind 应该检测我是否做了坏事,对吗?如果我 运行ning 内存不足,就会出现异常。我创建但从未 access/write/change 的 int 怎么可能被修改?很抱歉 post,但我不知道问题出在哪里。

没有完整性检查的 Valgrind 输出(键[999...9]):http://pastebin.com/hWH3fri2

第 155 行是 while (inputFile >> inputKey)

这是 clang 地址清理器的输出(在 setting it up properly 之后):

==15146==ERROR: AddressSanitizer: stack-buffer-overflow on address
0x7ffeb006bb80 at pc 0x0000004e093c bp 0x7ffeb006ba60 sp 0x7ffeb006ba58

WRITE of size 8 at 0x7ffeb006bb80 thread T0
    #0 0x4e093b in insertNode(int, int) skiplist.cpp:55:27
    #1 0x4e3385 in skiplist.cpp:160:9
    #2 0x7f40b2fcda3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x20a3f)
    #3 0x419508 in _start (a.out+0x419508)

Address 0x7ffeb006bb80 is located in stack of thread T0 at offset 160 in frame
    #0 0x4e022f in insertNode(int, int) skiplist.cpp:35

  This frams has 1 object(s):
    [32, 160) 'update' <== Memory access at offset 160 overflows this variable

第55行指的是:

void insertNode(int newKey, int newValue){
    node * update[MAXIMUMLEVEL];
    node * auxNode = list->header;
    for(int i=list->Maxlevel; i >=1; i--) {
        while ( auxNode->forward[i]->key < newKey ) {
            auxNode = auxNode->forward[i];
        }
        update[i] = auxNode;
    }
    auxNode = auxNode->forward[1];
    if ( auxNode->key == newKey ) {
        auxNode->value = newValue;
    } else {
        int randomLevel = 1;
        while ( (rand() / double(RAND_MAX)) < LEVELPROBABILITY && randomLevel < MAXIMUMLEVEL ) {
            randomLevel++;
        }

        if ( randomLevel > list->Maxlevel ) {
            for ( int i = list->Maxlevel+1; i <= randomLevel; i++ ) {
                update[i] = list->header; // line 55 <===================
            }
            list->Maxlevel = randomLevel;
        }

循环

while ( (rand() / double(RAND_MAX)) < LEVELPROBABILITY && randomLevel < MAXIMUMLEVEL ) {
    randomLevel++;
}

保证 randomLevel <= MAXIMUMLEVEL。如果randomLevel == MAXIMUMLEVEL,并且MAXIMUMLEVEL > list->Maxlevel,那么第54行的循环变成:

for ( int i = list->Maxlevel+1; i <= MAXIMUMLEVEL; i++ ) {
    update[i] = list->header; // line 55 <===================
}

请注意,update 声明为 node * update[MAXIMUMLEVEL];。您将获得越界访问权限。


我不太明白为什么你的代码似乎没有访问数组的第 0 个元素。根据我的经验,使用 [0, length_of_array) 形式的右半开范围也容易得多,这会导致

形式的循环
for(int i = 0; i < length_of_array; ++i)

请注意 < 而不是 <=。持续使用右侧半开范围可以显着减少差一错误的数量。

一个快速解决方法是声明 update 就像 node::forward 一样

node * update[MAXIMUMLEVEL + 1];

注意 +1

更好的解决方法可能是重写代码,使其使用右侧半开范围,其中 MAXIMUMLEVEL 从范围 [0, MAXIMUMLEVEL) 中获取它的解释并且不再是最大值,而是一个上确界(并表示层数)。