我的问题的标题可能是该网站上许多其他问题的标题,但是,我所经历的可能是我在整个学术生活中遇到过的最奇怪的事情。
我被分配为一个数据库设计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);
}
}
答案 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副本,好像它是一个简单的可复制类型,但事实并非如此。
您应该使用复制构造函数。此代码错误,将导致未定义的行为。
由于我看不到程序的其余部分,我无法判断这是否会导致问题,但这是一个可以开始的地方。