Search code examples
c++c++11vector

What could be causing the Segmentation Fault when attempting to print a 2D vector


This is a homework question. I am working on my Hash Table assignment and I ran into some issues when trying to print the table.

Constraints:

  • C++11
  • No std::map or std::unordered_map

Minimal Reproducible Example

#include <iostream>
#include <vector>
#include <atomic>
#include <ctime>
#include <iomanip>
#include <string>

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; //returns the hash
}

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);
  // DATE FORMAT: mm/dd/yy
  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(&current_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(&current_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;

  // Create two DataEntry objects
  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);

  // Add the entries to the CovidDB
  db.add(entry1);
  db.add(entry2);

  // Display the table
  db.display_table();

  // Clean up memory
  delete entry1;
  delete entry2;

  return 0;
}

The print function seems to work fine for 80% of the time (estimate), for instance when I am pushing data from .csv file into the 2D vector and print it works fine, but when I try to group 2 entries of the same hash

eg:

Country: Slovenia
Date: 01/01/23
Cases: 1
Deaths: 1
Country: Slovenia
Date: 02/02/23
Cases: 1
Deaths: 1

It segfaults...I am aware that SEGFAULT means one is trying to access memory that doesn't belong to him (usually trying to deref a nullptr)

I tried loads of stuff before asking a question here:

Rubber duck Debugging

I noticed that the outer loop runs hash-1 times by putting std::court << "here"; in-between the loops, which means that something goes wrong when it comes to the index of the entry.

I also changed auto to std::vecotr<DataEntry*> vec and to DataEntry* since the outer vector is a vector of vectors and the inner vectors are vectors of DataEntry pointers.

Debugging

I tried debugging the executable with gbd, since I can't configure my Vs Code debugger, but it throws an error

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

I googled it and the only solution I found was to root install these libraries, which I can't, since this is the school's server and running sudo will get me kicked off.

I tried the XCode debugger but for some reason my code there runs differently...Could it be because the ssh is Linux and the other one isn't?

Guards

I added "guards" to prevent printing an empty hash table, and a guard so that I'm not de-referencing a nullptr, unfortunately, had no effect.


Solution

  • Given the main program, one issue is in add(), at the very first line after the date code:

    int index = hash(entry->get_country());
    if (HashTable[index].empty()) {
        HashTable[index].push_back((entry));
    }
    

    The HashTable is an empty vector, yet you are accessing it at index, which is out-of-bounds.

    If you used at() instead of [], the error should become obvious.

    Example

    terminate called after throwing an instance of 'std::out_of_range'
      what():  vector::_M_range_check: __n (which is 6) >= this->size() (which is 0)
    

    Edit: Since you mentioned that the constructor resizes the internal vector to 17 entries, the other potential error is that the add() function delete's the entry that was passed, but your main program also delete's the same entry after add() is called.

    bool CovidDB::add(DataEntry* entry) {
    //...
    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;  // <-- This will issue a delete call on entry
            break;
    

    ...

    int main()
    {
       ...
       // Clean up memory
       delete entry1;
       delete entry2;
    

    The fix is to remove the delete in the add() function.