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

uqcuzwp8  于 2023-05-30  发布在  其他
关注(0)|答案(1)|浏览(134)

我正在实施一个跳过列表。它是什么并不重要,但它现在适用于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无效的内存写/读发生在变量改变之后,至少我是这样认为的。所以我才加了精神正常检查。正如我所想的,在尝试访问密钥[9999999999999999999999]之前,没有无效的内存写入/读取。但那一行只能运行int sanitycheck is changed,我从来没有这样做过。
最后,这里是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

我甚至不用马洛克。有指针操作,但valgrind应该检测到如果我做了什么不好的权利?如果内存不足,就会出现例外。我创建的一个int,从来没有访问/write/change,怎么可能被修改?很抱歉这篇文章很长,但我不知道问题出在哪里。
Valgrind未进行健全性检查的输出(keys[999...9]):http://pastebin.com/hWH3fri2
第155行是while(inputFile >> inputKey)

91zkwejq

91zkwejq1#

以下是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 == MAXIMUMLEVELMAXIMUMLEVEL > 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)

请注意<而不是<=。一致使用右侧半开范围可以显著减少偏一错误的数量。
一个快速解决方法是像node::forward一样将update声明为

node * update[MAXIMUMLEVEL + 1];

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

相关问题