Я реализую список пропуска. Не важно, что это такое, но сейчас оно работает для 1000 узлов, но не для 10000. Я получал SegFaults, которые не имели смысла, поэтому я напечатал некоторые переменные. К моему удивлению, многие вещи, которые не должны были меняться, превращались в мусорные ценности. Например, я напечатал inputValue до и после функции insertNode. Иногда он сбрасывается в ноль, когда всегда должен увеличиваться. Давайте посмотрим код (пропустите ввод прочитанного файла, проблема возникает во время цикла):
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);
Я управлял Вальгриндом. Неправильная запись / чтение памяти произошла после и из-за изменения переменных, по крайней мере, я так считаю. Вот почему я добавил проверку вменяемости. И, как я думал, не было недопустимых операций записи / чтения памяти перед попыткой доступа к ключам [9999999999999999999999]. Но эта строка может только запустить 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
Я даже не использую malloc. Есть операции с указателями, но valgrind должен обнаружить, если я сделал что-то плохое, верно? Если бы у меня не хватало памяти, было бы исключение. Как это возможно, что int, который я создаю и никогда не получаю доступ / запись / изменение, изменяется? Извините за длинный пост, но я понятия не имею, где проблема может быть.
Вывод Valgrind без проверки работоспособности (ключи [999 … 9]): http://pastebin.com/hWH3fri2
Строка 155 — время (inputFile >> inputKey)
Вот вывод дезинфицирующего средства clang (после настроить его правильно):
== 15146 == ОШИБКА: AddressSanitizer: переполнение буфера стека по адресу 0x7ffeb006bb80 на ПК 0x0000004e093c bp 0x7ffeb006ba60 sp 0x7ffeb006ba58 ЗАПИСЬ размера 8 на нити 0x7ffeb006bb80 T0 # 0 0x4e093b в insertNode (int, int) skiplist.cpp: 55: 27 # 1 0x4e3385 в skiplist.cpp: 160: 9 # 2 0x7f40b2fcda3f в __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x20a3f) # 3 0x419508 в _start (a.out + 0x419508) Адрес 0x7ffeb006bb80 находится в стеке потока T0 по смещению 160 в кадре # 0 0x4e022f в insertNode (int, int) skiplist.cpp: 35 Этот фрейм имеет 1 объект (ов): [32, 160) «Обновление» <== Доступ к памяти по смещению 160 переполняет эту переменную
Строка 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)
и больше не является максимумом, но является супремумом (и обозначает количество уровней).