打印二维vector时触发Segmentation Fault的原因排查(哈希表作业)
这是一道作业题,我正在完成Hash Table相关的作业,在尝试打印哈希表时遇到了问题。
约束条件
- C++11
- 禁止使用
std::map或std::unordered_map
最小可复现示例
#include <iostream> #include <vector> #include <atomic> #include <ctime> #include <iomanip> #include <string> #include <sstream> class DataEntry { public: std::string get_date() { return date; } std::string get_country() { return country; } int get_c_cases() { return c_cases; } int get_c_deaths() { return c_deaths; } inline void set_date(std::string set_date) { this->date = set_date;}; inline void set_country(std::string set_country) { this->country = set_country;}; inline void set_c_deaths(int set_c_deaths) { this->c_deaths = set_c_deaths;}; inline void set_c_cases(int set_c_cases) { this->c_cases = set_c_cases;}; private: std::string date; std::string country; int c_cases; int c_deaths; }; class CovidDB { private: std::vector<std::vector<DataEntry*>> HashTable; int size = 17; public: void display_table(); bool add(DataEntry* entry); int hash(std::string country); }; int CovidDB::hash(std::string country) { int sum = 0; int count = 0; for (char c : country) { sum = sum + ((count + 1) * c); count++; } return sum % size; } void CovidDB::display_table() { for (const auto& vec : HashTable) { for (const auto& entry : vec) { if (entry != nullptr) { std::cout << "[Date: " << entry->get_date() << "], " << "[Country: " << entry->get_country() << "], " << "[Cases: " << entry->get_c_cases() << "], " << "[Deaths: " << entry->get_c_deaths() << "]" << std::endl; } } } } bool CovidDB::add(DataEntry* entry) { time_t now = time(0); tm* ltm = localtime(&now); std::string current_date_str = std::to_string(1 + ltm->tm_mon) + "/" + std::to_string(ltm->tm_mday) + "/" + std::to_string(ltm->tm_year % 100); std::istringstream iss(current_date_str); std::tm current_date = {}; iss >> std::get_time(¤t_date, "%m/%d/%y"); std::tm entry_date = {}; std::istringstream iss2(entry -> get_date()); iss2 >> std::get_time(&entry_date, "%m/%d/%y"); if (mktime(¤t_date) > mktime(&entry_date)) { std::cout << "[Record rejected]" << std::endl; return false; } int index = hash(entry -> get_country()); if (HashTable[index].empty()) { HashTable[index].push_back((entry)); } else { bool added = false; for (DataEntry* existing_entry : HashTable[index]) { std::atomic<bool> valid(false); valid.store(hash(existing_entry->get_country()) == hash(entry->get_country()) && existing_entry->get_country() == entry->get_country()); if (valid) { existing_entry->set_date(entry -> get_date()); existing_entry->set_c_cases(existing_entry->get_c_cases() + entry->get_c_cases()); existing_entry->set_c_deaths(existing_entry->get_c_deaths() + entry->get_c_deaths()); added = true; delete entry; break; } } if (!added) { HashTable[index].push_back(entry); } } return true; return true; } int main() { CovidDB db; DataEntry* entry1 = new DataEntry(); entry1->set_date("01/01/23"); entry1->set_country("Slovenia"); entry1->set_c_cases(1); entry1->set_c_deaths(1); DataEntry* entry2 = new DataEntry(); entry2->set_date("02/02/23"); entry2->set_country("Slovenia"); entry2->set_c_cases(1); entry2->set_c_deaths(1); db.add(entry1); db.add(entry2); db.display_table(); delete entry1; delete entry2; return 0; }
问题描述
打印函数约80%场景下可正常工作,例如从.csv文件导入数据到二维vector后打印无异常,但合并两个相同hash值的条目时会触发Segmentation Fault:
Country: Slovenia Date: 01/01/23 Cases: 1 Deaths: 1
Country: Slovenia Date: 02/02/23 Cases: 1 Deaths: 1
我知道SEGFAULT通常意味着访问了非法内存(比如解引用nullptr)。
已尝试的调试方法
- 橡皮鸭调试:在外层循环间添加
std::cout << "here";,发现外层循环仅运行hash-1次,推测条目索引存在问题;尝试将auto改为std::vector<DataEntry*> vec和DataEntry*类型,无效。 - 调试工具:用
gdb调试时,学校服务器无权限安装依赖库,报错:
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-211.el8.x86_64 libgcc-8.5.0-16.el8_7.x86_64 libstdc++-8.5.0-16.el8_7.x86_64
改用XCode调试,运行表现与Linux服务器不同,怀疑跨环境差异导致。
- 添加防护逻辑:增加防止打印空哈希表、避免解引用
nullptr的代码,未解决问题。
问题根源及修复方案
1. 哈希表未初始化大小
CovidDB中的HashTable是std::vector<std::vector<DataEntry*>>,默认是空容器。当调用hash得到索引后直接访问HashTable[index],会触发越界访问,这是未定义行为,直接导致段错误。
修复:给CovidDB添加构造函数,初始化哈希表的桶数量:
CovidDB() { HashTable.resize(size); }
2. 双重释放内存
add函数中,当合并相同国家的条目时,会执行delete entry;(比如示例中的entry2会被释放),但main函数仍会执行delete entry2;,这会触发双重释放,导致段错误。
修复:注释掉main中的delete entry2;,或者改用智能指针(如C++11的std::unique_ptr)管理内存。
3. 冗余的原子变量
代码中无多线程场景,std::atomic<bool>完全多余,反而可能引入不必要的开销,直接用普通bool即可:
bool valid = (hash(existing_entry->get_country()) == hash(entry->get_country()) && existing_entry->get_country() == entry->get_country());
4. 无效的重复返回
add函数末尾有两个return true;,第二个永远不会执行,属于无效代码,直接删除。
5. 缺失的头文件
原代码使用了std::istringstream但未包含<sstream>头文件,某些编译器可能隐式包含,但标准不保证,需手动添加:
#include <sstream>
6. 多余的空容器判断
原代码中if (HashTable[index].empty())的判断无意义,直接遍历查找即可,找不到就添加,简化逻辑:
bool added = false; for (DataEntry* existing_entry : HashTable[index]) { bool valid = (hash(existing_entry->get_country()) == hash(entry->get_country()) && existing_entry->get_country() == entry->get_country()); if (valid) { existing_entry->set_date(entry -> get_date()); existing_entry->set_c_cases(existing_entry->get_c_cases() + entry->get_c_cases()); existing_entry->set_c_deaths(existing_entry->get_c_deaths() + entry->get_c_deaths()); added = true; delete entry; break; } } if (!added) { HashTable[index].push_back(entry); }
内容的提问来源于stack exchange,提问作者i_hate_F_sharp

