如何定位并解决双向游标链表数据结构中出现的Heisenbug?
双向链表拷贝构造随机段错误(Heisenbug)排查
我正在实现一款修改版双向链表数据结构,在对功能做单元测试时遇到了一个无法解决的Heisenbug。
问题复现代码
触发随机段错误的测试代码如下:
case CURSOR_LIST_COPY_CONSTRUCTOR: { // Test to construct a cursor list by using the copy constructor. if (verbose) { os << "\nCURSOR_LIST_COPY_CONSTRUCTOR:" << std::endl << "Starting the construction operation:" << std::endl; } anil::cursor_list my_cursor_list; my_cursor_list.append(1); anil::cursor_list my_copied_cursor_list = my_cursor_list; if (&my_copied_cursor_list == nullptr && my_copied_cursor_list.index() != -1 && my_copied_cursor_list.size() != 1 && my_copied_cursor_list.front_data() != 1 && my_copied_cursor_list.back_data() != 1) { if (verbose) { os << "Copy construction unsuccessful!" << std::endl; } return false; } else { if (verbose) { os << "Copy construction successful!" << std::endl; } return true; } return false; break; }
每运行10次约有2次会在my_copied_cursor_list.front_data() != 1这一行触发段错误。一开始无法在gdb中复现该bug,参照同类问题解决方案,在gdb中执行set disable-randomization off命令后成功复现了相同的bug。
相关函数源码
front_data 函数
int anil::cursor_list::front_data() { if (this != nullptr && this->is_empty() == false) { return this->front->data; } }
曾尝试新增this->front非空判断,修改后依然触发段错误,修改后的代码如下:
int anil::cursor_list::front_data() { if (this != nullptr && this->is_empty() == false) { if (this->front != nullptr) { return this->front->data; } } }
append 函数
void anil::cursor_list::append(int new_data) { if (this != nullptr) { cursor_list_node* new_node = new cursor_list_node; new_node->data = new_data; new_node->next = nullptr; new_node->previous = this->back; if (this->is_empty() == false) { this->back->next = new_node; } else { this->front = new_node; } this->back = new_node; ++this->m_size; } }
类声明代码
#ifndef ANIL_CURSOR_LIST_H #define ANIL_CURSOR_LIST_H #include <cstddef> #include <iostream> namespace anil { class cursor_list_node { private: int data; cursor_list_node* next; cursor_list_node* previous; friend class cursor_list; }; class cursor_list { private: // Data: int m_index; int m_size; cursor_list_node* front; cursor_list_node* back; cursor_list_node* cursor; // Functions: void delete_list(); public: cursor_list() : m_index(-1), m_size(0), front(nullptr), back(nullptr), cursor(nullptr) {} cursor_list(cursor_list& copied_list); bool is_empty(); int size(); int index(); int front_data(); int back_data(); int cursor_data(); bool operator==(cursor_list& rhs); // rhs = right hand side cursor_list& operator= (cursor_list& rhs); // rhs = right hand side friend std::ostream& operator<<(std::ostream& out, const cursor_list& rhs); // rhs = right hand side void clear(); void move_cursor_front(); void move_cursor_back(); void move_cursor_prev(); void move_cursor_next(); void prepend(int new_data); void append(int new_data); void insert_before_cursor(int new_data); void insert_after_cursor(int new_data); void delete_front(); void delete_back(); void delete_cursor(); ~cursor_list(); }; } #endif /* ANIL_CURSOR_LIST_H */
报错信息
gdb中的段错误提示如下:
Program received signal SIGSEGV, Segmentation fault. 0x00000000004018c8 in anil::cursor_list::front_data (this=0x7ffe055100c0) at anil_cursor_list.cpp:82 82 return this->front->data;
运行环境为Ubuntu 16.04.6 LTS。
调试过程
使用gdb的next和step命令逐行运行程序,在段错误发生前打印了this和this->front的值,结果如下:
Breakpoint 1, run_tests (os=..., bst_test=1, verbose=false) at main.cpp:121 121 anil::cursor_list my_copied_cursor_list = my_cursor_list; (gdb) next 138 if (my_copied_cursor_list.index() != -1) {} (gdb) 139 if (my_copied_cursor_list.size() != 1) {} (gdb) 140 if (my_copied_cursor_list.front_data() != 1) {} (gdb) step anil::cursor_list::front_data (this=0x7ffc683c4500) at anil_cursor_list.cpp:79 79 if (this != nullptr && this->is_empty() == false) { (gdb) next 80 return this->front->data; (gdb) print this $7 = (anil::cursor_list * const) 0x7ffc683c4500 (gdb) print this->front $8 = (anil::cursor_list_node *) 0xe92cb976bc5a2900 (gdb) next Program received signal SIGSEGV, Segmentation fault. 0x0000000000401956 in anil::cursor_list::front_data (this=0x7ffc683c4500) at anil_cursor_list.cpp:80 80 return this->front->data;
问题解决
该Heisenbug的根本原因是拷贝构造函数未初始化类成员变量,修改拷贝构造函数,在执行拷贝逻辑前先初始化所有成员变量后,段错误不再复现。修改后的拷贝构造函数代码如下:
this->m_index = -1; this->m_size = 0; this->front = nullptr; this->back = nullptr; this->cursor = nullptr; if (copied_cursor_list.is_empty() == false) { for (cursor_list_node* it = copied_cursor_list.front; it != nullptr; it = it->next) { this->append(it->data); } }
内容的提问来源于stack exchange,提问作者Anil Celik Maral
相关产品推荐
相关产品推荐

