在C ++中从文件加载数据时出现分段错误

时间:2014-03-23 07:17:52

标签: c++ segmentation-fault binary-search-tree

我的问题的标题可能是该网站上许多其他问题的标题,但是,我所经历的可能是我在整个学术生活中遇到过的最奇怪的事情。

我被分配为一个数据库设计binary search tree,该数据库包含公司员工记录的长列表。我已成功实施该项目,一切正常,直到我收到教授发来的电子邮件,要求课堂上的每个人通过SSH登录Linux驱动的服务器并验证项目是否按预期进行编译和行为。

源代码编译得很好,但是,当我运行应用程序并告诉它加载文件的3000条记录列表时,我在第543条记录(文件中的行)中遇到segmentation fault

我的问题是:考虑到代码在我自己的机器上工作正常,可能导致此问题的原因是什么?

对于项目的大小,重要的是我分配了多少内存?是否有可能在加载数据时内存不足?

即使我100%确定问题不在我的代码中,我仍然认为您可以方便地查看一段代码并尝试发现问题。

这是我的employee课程:

class employee
{
public:

    employee(){}

    employee(std::string first_name, std::string last_name, unsigned int ID)
    {
        this->first_name = first_name;
        this->last_name = last_name;
        this->ID = ID;
    }

    std::string first_name;
    std::string last_name;
    unsigned int ID;
    employee * left, * right;
};//*root;

这是我用来将文件加载到数据库中的函数:

void createBST(bstree & tree)
{
    string file_name;

    cout << "Enter database file path: ";
    cin >> file_name;

    ifstream file_reader (file_name.c_str());

  //  if (file_reader == NULL)
  //  {
   //     error("\nError: Invalid file path provided!\n\n");
   ///     return;
   // }

    string line;
    for (;;)
    {
        if (!getline(file_reader, line))
            break;

        string tokens[3];
        split(line, tokens);

        string first_name, last_name;
        unsigned int ID;

        last_name = tokens[0];
        first_name = tokens[1];
        ID = std::atoi(tokens[2].c_str());

    //    cout << "INSERTING: " << tokens[2] << "\t" << tokens[0] << "\t" << tokens[1] << endl;

        // insert a new employee object into bstree
        tree.Insert(new employee(first_name, last_name, ID));
    }

    cout << endl << endl << "All employee records have been inserted to database successfully." << endl << endl;

    // close file
    file_reader.close();
}

这是我的binary search tree(bstree):

#include <iomanip>
#include <iostream>
#include "bstree.h"

//--------------------------------------------
// Function: bstree()
// Purpose: Class constructor.
//--------------------------------------------
bstree::bstree()
{
    count = 0;
    root = NULL;
}

//--------------------------------------------
// Function: ~bstree()
// Purpose: Class destructor.
//--------------------------------------------
bstree::~bstree()
{
    ClearTree(root);
    return;
}

//--------------------------------------------
// Function: ClearTree()
// Purpose: Perform a recursive traversal of
//        a tree destroying all nodes.
//--------------------------------------------
void bstree::ClearTree(employee *T)
{
    if(T==NULL) return;  // Nothing to clear
    if(T->left != NULL) ClearTree(T->left); // Clear left sub-tree
    if(T->right != NULL) ClearTree(T->right); // Clear right sub-tree
    delete T;    // Destroy this node
    return;
}

//--------------------------------------------
// Function: isEmpty()
// Purpose: Return TRUE if tree is empty.
//--------------------------------------------
bool bstree::isEmpty()
{
    return(root == NULL);
}


//--------------------------------------------
// Function: DupNode()
// Purpose: Duplicate a node in the tree.  This
//        is used to allow returning a complete
//        structure from the tree without giving
//        access into the tree through the pointers.
// Preconditions: None
// Returns: Pointer to a duplicate of the node arg
//--------------------------------------------
employee *bstree::DupNode(employee * T)
{
    employee *dupNode;

    dupNode = new employee();
    *dupNode = *T;    // Copy the data structure
    dupNode->left = NULL;    // Set the pointers to NULL
    dupNode->right = NULL;
    return dupNode;
}


//--------------------------------------------
// Function: SearchTree()
// Purpose: Perform an iterative search of the tree and
//        return a pointer to a treenode containing the
//        search key or NULL if not found.
// Preconditions: None
// Returns: Pointer to a duplicate of the node found
//--------------------------------------------
employee *bstree::SearchTree(unsigned int Key)
{
    employee *temp;

    temp = root;
    while((temp != NULL) && (temp->ID != Key))
    {
        if(Key < temp->ID)
            temp = temp->left;  // Search key comes before this node.
        else
            temp = temp->right; // Search key comes after this node
    }

    if(temp == NULL)
        return temp;    // Search key not found
    else
        return(DupNode(temp));    // Found it so return a duplicate
}



//--------------------------------------------
// Function: Insert()
// Insert a new node into the tree.
// Preconditions: None
// Returns: int (TRUE if successful, FALSE otherwise)
//--------------------------------------------
bool bstree::Insert(employee *newNode)
{
    employee *temp;
    employee *back;

    temp = root;
    back = NULL;

    while(temp != NULL) // Loop till temp falls out of the tree
    {
        back = temp;
        if(newNode->ID < temp->ID)
            temp = temp->left;
        else if (newNode->ID > temp->ID)
            temp = temp->right;
        else
            return false;
    }

    // Now attach the new node to the node that back points to
    if(back == NULL) // Attach as root node in a new tree
        root = newNode;

    else
    {
        if(newNode->ID < back->ID)
            back->left = newNode;
        else if (newNode->ID > back->ID)
            back->right = newNode;
        else
            return false;
    }

   return true;
}


//--------------------------------------------
// Function: Insert()
// Insert a new node into the tree.
// Preconditions: None
// Returns: int (TRUE if successful, FALSE otherwise)
//--------------------------------------------
bool bstree::Insert(unsigned int Key, string first_name, string last_name)
{
     employee *newNode;

    // Create the new node and copy data into it
    newNode = new employee();
    newNode->ID = Key;
    newNode->first_name = first_name;
    newNode->last_name = last_name;
    newNode->left = newNode->right = NULL;

    // Call other Insert() to do the actual insertion
    return(Insert(newNode));
}

//--------------------------------------------
// Function: Delete()
// Purpose: Delete a node from the tree.
// Preconditions: Tree contains the node to delete
// Returns: int (TRUE if successful, FALSE otherwise)
//--------------------------------------------
bool bstree::Delete(unsigned int Key)
{
    employee *back;
    employee *temp;
    employee *delParent;    // Parent of node to delete
    employee *delNode;      // Node to delete

    temp = root;
    back = NULL;

    // Find the node to delete
    while((temp != NULL) && (Key != temp->ID))
    {
        back = temp;
        if(Key < temp->ID)
            temp = temp->left;
        else
            temp = temp->right;
    }

    if(temp == NULL) // Didn't find the one to delete
        return false;

    else
    {
        if(temp == root) // Deleting the root
        {
            delNode = root;
            delParent = NULL;
        }
        else
        {
            delNode = temp;
            delParent = back;
        }
    }

    // Case 1: Deleting node with no children or one child
    if(delNode->right == NULL)
    {
        if(delParent == NULL)    // If deleting the root
        {
            root = delNode->left;
            delete delNode;
            return true;
        }
        else
        {
            if(delParent->left == delNode)
                delParent->left = delNode->left;
            else
                delParent->right = delNode->left;
                delete delNode;
            return true;
        }
    }
    else // There is at least one child
    {
        if(delNode->left == NULL)    // Only 1 child and it is on the right
        {
            if(delParent == NULL)    // If deleting the root
            {
                root = delNode->right;
                delete delNode;
                return true;
            }
            else
            {
                if(delParent->left == delNode)
                    delParent->left = delNode->right;
                else
                    delParent->right = delNode->right;
                delete delNode;
                return true;
            }
        }
        else // Case 2: Deleting node with two children
        {
            // Find the replacement value.  Locate the node
            // containing the largest value smaller than the
            // key of the node being deleted.
            temp = delNode->left;
            back = delNode;
            while(temp->right != NULL)
            {
                back = temp;
                temp = temp->right;
            }
            // Copy the replacement values into the node to be deleted
            delNode->ID = temp->ID;
            delNode->first_name = temp->first_name;
            delNode->last_name = temp->last_name;

            // Remove the replacement node from the tree
            if(back == delNode)
                back->left = temp->left;
            else
                back->right = temp->left;
            delete temp;
            return true;
        }
    }
}


//--------------------------------------------
// Function: PrintOne()
// Purpose: Print data in one node of a tree.
// Preconditions: None
// Returns: void
//--------------------------------------------
void bstree::PrintOne(employee *T)
{
    cout << T->ID << "\t\t" << T->first_name << "\t\t" << T->last_name << endl;
}



//--------------------------------------------
// Function: PrintAll()
// Purpose: Print the tree using a recursive
//        traversal
// Preconditions: None
// Returns: void
//--------------------------------------------
void bstree::PrintAll(employee *T)
{
    if(T != NULL)
    {
        PrintAll(T->left);
        PrintOne(T);
        PrintAll(T->right);
    }
}


//--------------------------------------------
// Function: PrintTree()
// Purpose: Print the tree using a recursive
//        traversal.  This gives the user access
//        to PrintAll() without giving access to
//        the root of the tree.
// Preconditions: None
// Returns: void
//--------------------------------------------
void bstree::PrintTree()
{
    PrintAll(root);
}


void bstree::saveToFile(const char * fileName)
{
    ofstream file_writer;
    file_writer.open(fileName);
    saveToFile(file_writer, root);
    file_writer.close();
}

void bstree::saveToFile(ofstream & file_writer, employee * T)
{
    if (T != NULL)
    {
        saveToFile(file_writer, T->left);
        file_writer << T->last_name;
        file_writer << "\t";
        file_writer << T->first_name;
        file_writer << "\t";
        file_writer << T->ID;
        file_writer << "\n";
        saveToFile(file_writer, T->right);
    }

}

3 个答案:

答案 0 :(得分:2)

employee构造函数都没有将左指针或右指针初始化为NULL。这对于复制构造函数来说尤其令人担忧,但参数化的构造函数是痛苦将真正显示的地方:

从文件加载时,执行以下操作:

tree.Insert(new employee(first_name, last_name, ID));

触发此构造函数:

employee(std::string first_name, std::string last_name, unsigned int ID)
{
    this->first_name = first_name;
    this->last_name = last_name;
    this->ID = ID;
}

上面没有任何内容分配给任何东西的左右成员指针。因此,它们不确定因此是垃圾。所以当你这样做时:

bool bstree::Insert(employee *newNode)
{
    employee *temp;
    employee *back;

    temp = root;
    back = NULL;

    while(temp != NULL) // Loop till temp falls out of the tree
    {
        back = temp;
        if(newNode->ID < temp->ID)
            temp = temp->left;
        else if (newNode->ID > temp->ID)
            temp = temp->right;
        else
            return false;
    }

    // Now attach the new node to the node that back points to
    if(back == NULL) // Attach as root node in a new tree
        root = newNode;

    else
    {
        if(newNode->ID < back->ID)
            back->left = newNode;
        else if (newNode->ID > back->ID)
            back->right = newNode;
        else
            return false;
    }

   return true;
}

你正在追逐无效的指针,甚至在不调用未定义的行为的情况下甚至不能评估更少解除引用。

将变为在线调试会话。您需要正确初始化对象类的所有成员,最好是在初始化列表中:

class employee
{
public:

    // note: this shouldn't even be *needed*
    employee() : ID(), left(), right() {}

    // parameterized constructor
    employee(const std::string& first, const std::string& last, unsigned int id)
        : first_name(first)
        , last_name(last)
        , ID(id)
        , left(), right()
    {
    }

     // copy-ctor. retains values; child pointers set as null
    employee(const employee& obj)
        : first_name(obj.first_name)
        , last_name(obj.last_name)
        , ID(obj.id)
        , left(), right()
    {
    }

    // assignment operator. does NOT copy child pointers
    employee& operator =(const employee& obj)
    {
        first_name = obj.first_name;
        last_name = obj.last_name;
        ID = obj.ID;
    }

    std::string first_name;
    std::string last_name;
    unsigned int ID;
    employee * left, * right;
};

通常我会通过实现copy/swap idiom来编码赋值运算符以使用copy-constructor。但是在这种情况下它会有点过分,因为你的对象没有实际的动态管理成员(即成员对象本身实际上负责创建/销毁)。

无论如何,上面是一个问题,我没有花时间去剖析插入逻辑之外的实际树管理代码。如果在删除操作中潜伏着一个缺陷,我不会感到惊讶,这对于二叉树来说总是很乏味。但这应该足以让你走得更远。

答案 1 :(得分:1)

  

我的问题是:考虑到代码在我自己的机器上工作正常,可能导致此问题的原因是什么?

有几种可能性。您的程序可能遇到某些资源限制(例如内存)。它也可能展示undefined behaviour

我要做的第一件事就是从崩溃中获取堆栈跟踪。

此外,我会在valgrind下运行该程序,看它是否发现任何问题。

答案 2 :(得分:1)

你正在做一份浅浅的Employee副本,好像它是一个简单的可复制类型,但事实并非如此。

您应该使用复制构造函数。此代码错误,将导致未定义的行为。

由于我看不到程序的其余部分,我无法判断这是否会导致问题,但这是一个可以开始的地方。