单链表析构触发double free(tcache2)及SIGABRT错误排查
我正在为课程作业实现一个仅支持头插和尾删的单链表,配合栈完成回文字符串验证。功能运行正常,但调用~List()析构函数时总会触发“double free in tcache2”错误和SIGABRT信号,调试器未提供有效信息,查阅类似问题后仍无法解决。以下是相关代码及Valgrind报错信息,恳请帮忙排查原因:
单链表内存操作相关代码
template <typename T> List<T>::~List() { Node<T> *temp = head; while (head != nullptr) { temp = head; head = head->next; delete temp; } } template <typename T> void List<T>::insert_element(T data) { Node<T> *newNode = new Node<T>; newNode->data = data; newNode->next = head; head = newNode; } template <typename T> T List<T>::delete_element() { T data; Node<T> *temp = head; if (head->next == nullptr) { data = head->data; delete head; head = nullptr; return data; } while (temp->next->next != nullptr) { temp = temp->next; } data = temp->next->data; delete temp->next; temp->next = nullptr; return data; }
主程序代码
bool checkPalindrome(List<char> l, Stack<char> s, int string_len) { for (size_t i = 0; i < string_len; i++) { if(l.delete_element()!=s.pop()) return false; } return true; } int main() { List<char> characters_l; Stack<char> characters_s; string input = "kajak"; for (size_t i = 0; i < input.length(); i++) { characters_l.insert_element(input[i]); characters_s.push(input[i]); } std::cout<<input<<" is "<<(checkPalindrome(characters_l, characters_s, input.length()) ? "" : "not ") <<"a palindrome"<<std::endl; return 0; }
Valgrind报错信息
==21949== Invalid read of size 8 ==21949== at 0x10AA83: List<char>::~List() (list.hpp:36) ==21949== by 0x10A727: main (lab1b.cpp:30) ==21949== Address 0x4de0e18 is 8 bytes inside a block of size 16 free'd ==21949== at 0x484C91D: operator delete(void*, unsigned long) (vg_replace_malloc.c:1101) ==21949== by 0x10AAA3: List<char>::~List() (list.hpp:37) ==21949== by 0x10A6EF: main (lab1b.cpp:27) ==21949== ==21949== Invalid free() / delete / delete[] / realloc() ==21949== at 0x484C91D: operator delete(void*, unsigned long) (vg_replace_malloc.c:1101) ==21949== by 0x10AAA3: List<char>::~List() (list.hpp:37) ==21949== by 0x10A727: main (lab1b.cpp:30) ==21949== Address 0x4de0e10 is 0 bytes inside a block of size 16 free'd ==21949== at 0x484C91D: operator delete(void*, unsigned long) (vg_replace_malloc.c:1101) ==21949== by 0x10AAA3: List<char>::~List() (list.hpp:37) ==21949== by 0x10A6EF: main (lab1b.cpp:27)
问题原因
核心问题是未为List类实现拷贝构造函数和拷贝赋值运算符,违反了C++的三/五法则(自定义析构函数时,必须同时自定义拷贝构造和拷贝赋值,避免浅拷贝导致的内存问题)。
调用checkPalindrome(characters_l, ...)时,characters_l以值传递方式传入函数,触发List的默认拷贝构造函数。默认拷贝是浅拷贝,直接复制head指针,导致原对象characters_l和函数内临时对象l共享同一份链表节点内存。
函数执行完毕后,临时对象l销毁,析构函数释放所有节点内存,此时原对象characters_l的head指针变为悬空指针。main函数结束时,原对象characters_l销毁,析构函数再次尝试释放已被释放的节点,直接触发double free错误。
解决方案
方案1:改用引用传递参数
修改checkPalindrome函数的参数为引用,避免创建临时拷贝对象:
bool checkPalindrome(List<char>& l, Stack<char>& s, int string_len)
此方法简单直接,不会触发两次析构,从根源避免double free。
方案2:实现深拷贝的拷贝构造和拷贝赋值
为List类添加深拷贝逻辑,确保每个对象拥有独立的链表节点内存:
// 拷贝构造函数 template <typename T> List<T>::List(const List<T>& other) : head(nullptr) { Node<T>* current = other.head; Node<T>* tail = nullptr; while (current != nullptr) { Node<T>* newNode = new Node<T>; newNode->data = current->data; newNode->next = nullptr; if (head == nullptr) { head = newNode; tail = newNode; } else { tail->next = newNode; tail = newNode; } current = current->next; } } // 拷贝赋值运算符(遵循copy-and-swap惯用法) template <typename T> List<T>& List<T>::operator=(List<T> other) { std::swap(head, other.head); return *this; }
额外优化点
delete_element函数存在空指针风险:当链表为空时,head是nullptr,访问head->next会触发崩溃,建议添加空链表判断:
template <typename T> T List<T>::delete_element() { if (head == nullptr) { throw std::runtime_error("无法从空链表中删除元素"); } // 原代码逻辑... }
内容的提问来源于stack exchange,提问作者Juliusz Bednarek

