Tricky Segmentation在C中的BST递归出错

我正在尝试使用递归插入方法(通常用于BST,IIRC)将字符串添加到二进制搜索树中,以便稍后我也可以使用递归将它们打印出来。

麻烦的是,我一直在得到一个我不太懂的分段错误。 相关代码如下(这段代码来自我的主函数):

#include  #include  #include  #include  // Stores the size of the C-strings we will use; // Standardized to 100 (assignment specifications say // ALL strings will be no more than 100 characters long) // Please note that I defined this as a preprocessor // directive because using the const keyword makes it // impossible to define the size of the C-String array // (C doesn't allow for static array struct members whose // size is given as a variable) #define STRING_SIZE 100 // The flags for case sensitivity // and an output file int cflag = 0, oflag = 0; // These are intended to represent the boolean // values true and false (there's no native bool // data type in C, apparently) const int TRUE = 1; const int FALSE = 0; // Type alias for the bool type typedef int bool; // This is the BST struct. A BST is basically just // a Node with at most two children (left and right) // and a data element. typedef struct BST { struct BST *left; struct BST *right; char *key; int counter; } BST; // ----- FUNCTION PROTOTYPES ----- void insert(BST **root, char *key); int caseSenStrCmp(char *str1, char *str2); int caseInsenStrCmp(char *str1, char *str2); bool existsInTree(BST *root, char *key, int cflag); void inOrderPrint(BST *root, int oflag, FILE *outFile); void deallocateTree(BST *root); int main(int argc, char **argv) { extern char *optarg; extern int optind; int c, err = 0; // Holds the current line in the file/user-provided string. char currentLine[STRING_SIZE]; // This will store the input/output file // directories char fileDirectory[STRING_SIZE]; static char usage[] = "Usage: %s [-c] [-o output_file_name] [input_file_name]\n"; while ((c = getopt(argc, argv, "co:")) != -1) switch (c) { case 'c': cflag = 1; break; case 'o': oflag = 1; // If an output file name // was entered, copy it // to fileDirectory if (argv[optind] != NULL) { strcpy(fileDirectory, argv[optind]); } break; case '?': err = 1; break; default: err = 1; break; } if (err) { // Generic error message printf("ERROR: Invalid input.\n"); fprintf(stderr, usage, argv[0]); exit(1); } // --- BST SORT CODE STARTS HERE --- printf("This is BEFORE setting root to NULL\n"); // This is the BST. As the assignment instructions // specify, it is initially set to NULL BST *root = NULL; // Pointer to the mode the files // will be opened in. Starts as // "w" since we're opening the output file // first printf("This is AFTER setting root to NULL\n"); char *mode = (char*)malloc(sizeof(char*)); strcpy(mode, "w"); printf("Wrote w to mode pointer"); // Pointer to the output file FILE *outFile; // Attempt to open output file outFile = fopen(fileDirectory, mode); printf("Opened outfile \n"); // Now update mode and fileDirectory so // we can open the INPUT file strcpy(mode, "r"); printf("Wrote r to mode\n"); // Check if we have an input file name // If argv[optind] isn't NULL, that means we have // an input file name, so copy it into fileDirectory if (argv[optind] != NULL) { strcpy(fileDirectory, argv[optind]); } printf("Wrote input file name to fileDirectory.\n"); // Pointer to the input file FILE *inFile; // Attempt to open the input file //printf("%d", inFile = fopen(fileDirectory, mode)); printf("Opened input file\n"); // If the input file was opened successfully, process it if (inFile != NULL) { // Process the file while EOF isn't // returned while (!feof(inFile)) { // Get a single line (one string) //fgets(currentLine, STRING_SIZE, inFile); printf("Wrote to currentLine; it now contains: %s\n", currentLine); // Check whether the line is an empty line if (*currentLine != '\n') { // If the string isn't an empty line, call // the insert function printf("currentLine wasn't the NULL CHAR"); insert(&root, currentLine); } } // At this point, we're done processing // the input file, so close it fclose(inFile); } // Otherwise, process user input from standard input else { do { printf("Please enter a string (or blank line to exit): "); // Scanf takes user's input from stdin. Note the use // of the regex [^\n], allowing the scanf statement // to read input until the newline character is encountered // (which happens when the user is done writing their string // and presses the Enter key) scanf("%[^\n]s", currentLine); // Call the insert function on the line // provided insert(&root, currentLine); } while (caseSenStrCmp(currentLine, "") != 0); } // At this point, we've read all the input, so // perform in-order traversal and print all the // strings as per assignment specification inOrderPrint(root, oflag, outFile); // We're done, so reclaim the tree deallocateTree(root); } // ===== AUXILIARY METHODS ====== // Creates a new branch for the BST and returns a // pointer to it. Will be called by the insert() // function. Intended to keep the main() function // as clutter-free as possible. BST* createBranch(char *keyVal) { // Create the new branch to be inserted into // the tree BST* newBranch = (BST*)malloc(sizeof(BST)); // Allocate memory for newBranch's C-string newBranch->key = (char*)malloc(STRING_SIZE * sizeof(char)); // Copy the user-provided string into newBranch's // key field strcpy(newBranch->key, keyVal); // Set newBranch's counter value to 1. This // will be incremented if/when other instances // of the key are inserted into the tree newBranch->counter = 1; // Set newBranch's child branches to null newBranch->left = NULL; newBranch->right = NULL; // Return the newly created branch return newBranch; } // Adds items to the BST. Includes functionality // to verify whether an item already exists in the tree // Note that we pass the tree's root to the insert function // as a POINTER TO A POINTER so that changes made to it // affect the actual memory location that was passed in // rather than just the local pointer void insert(BST **root, char *key) { printf("We made it to the insert function!"); // Check if the current branch is empty if (*root == NULL) { // If it is, create a new // branch here and insert it // This will also initialize the // entire tree when the first element // is inserted (ie when the tree is // empty) *root = createBranch(key); } // If the tree ISN'T empty, check whether // the element we're trying to insert // into the tree is already in it // If it is, don't insert anything (the // existsInTree function takes care of // incrementing the counter associated // with the provided string) if (!existsInTree(*root, key, cflag)) { // If it isn't, check if the case sensitivity // flag is set; if it is, perform the // checks using case-sensitive string // comparison function if (cflag) { // Is the string provided (key) is // greater than the string stored // at the current branch? if (caseSenStrCmp((*root)->key, key)) { // If so, recursively call the // insert() function on root's // right child (that is, insert into // the right side of the tree) // Note that we pass the ADDRESS // of root's right branch, since // the insert function takes a // pointer to a pointer to a BST // as an argument insert(&((*root)->right), key); } // If not, the key passed in is either less than // or equal to the current branch's key, // so recursively call the insert() // function on root's LEFT child (that is, // insert into the left side of the tree) else { insert(&((*root)->left), key); } } // If it isn't, perform the checks using // the case-INsensitive string comparison // function else { // The logic here is exactly the same // as the comparisons above, except // it uses the case-insensitive comparison // function if (caseInsenStrCmp((*root)->key, key)) { insert(&((*root)->right), key); } else { insert(&((*root)->left), key); } } } } // CASE SENSITIVE STRING COMPARISON function. Returns: // -1 if str1 is lexicographically less than str2 // 0 if str1 is lexicographically equal to str2 // 1 if str2 is lexicographically greater than str1 

我正在使用getopt来解析用户输入的选项。 我一直在使用printf语句做一些基本的调试,只是为了看看我在崩溃之前进入代码的程度,并且我或多或少地缩小了原因。 这似乎是这里的一部分:

 do { printf("Please enter a string (or blank line to exit): "); // Scanf takes user's input from stdin. Note the use // of the regex [^\n], allowing the scanf statement // to read input until the newline character is encountered // (which happens when the user is done writing their string // and presses the Enter key) scanf("%[^\n]s", currentLine); // Call the insert function on the line // provided insert(&root, currentLine); } while (caseSenStrCmp(currentLine, "\n") != 0); 

或者更确切地说,通常调用insert函数,因为我在insert函数的开头放置的printf语句(“我们使它成为插入函数!”)一遍又一遍地打印,直到程序最终崩溃并出现分段错误,这可能意味着问题是无限递归?

如果是这样,我不明白为什么会这样。 我在main的开头将根节点初始化为NULL,所以它应该直接进入insert函数* root == NULL case,至少在它的第一次调用时。

它可能与我传递root作为指针指针(插入函数的参数列表中的BST **根)的方式有关吗? 我不正确地递归,即这个陈述(和其他类似的陈述)

  insert(&((*root)->right), key); 

不知怎的错? 这是我的第一个猜测,但是我没有看到它会如何导致无限递归 – 如果有的话,它应该会失败而不会再次递归,如果是这样的话? 无论哪种方式,它都无法解释为什么当root为NULL时会发生无限递归(即在第一次调用insert时,其中我传入&root – 指向根指针的指针 – 插入函数)。

我真的坚持这个。 起初我认为这可能与我将字符串复制到currentLine的方式有关,因为该行

 if(*currentLine != '\0') 

在while(!feof(inFile))循环中,程序也会因为分段错误而崩溃,但即使我评论整个部分只是为了测试剩下的代码,我最终得到了无限递归问题。

任何帮助都在这里受到赞赏,我一直试图解决这个问题超过5个小时无济于事。 我合法地不知道该怎么做。

**编辑:由于很多评论涉及到我在其余代码中声明其他变量的方式的问题,我决定包括我的整个代码,至少在insert()函数之前,问题(大概是)。 我只是省略了尝试将代码保持在最低限度的东西 – 我确信没有人喜欢阅读大块代码。

Barmar:

关于fopen()和fgets():这些被注释掉,因此inFile将保持为NULL并且相关的条件检查将失败,因为代码的那部分也会因为分段错误而失败

createBranch()会将它创建的节点的左右子节点初始化为NULL(如上所示)

currentLine声明为具有静态大小的数组。

@coderredoc:

我对它的理解是它从标准输入读取,直到遇到换行符(即用户点击输入按钮),这不是作为字符串的一部分记录的

我已经可以看到你要去哪里了! 我的循环条件设置为do / while循环设置为检查换行符,因此循环永远不会终止。 那绝对是我的错; 这是我忘记改变的那个块的先前实现的遗留物。

我确实在你指出之后改变它(参见上面的新代码),但遗憾的是它没有解决问题(我猜它是因为insert()函数内部发生了无限递归 – 一旦它被调用了第一个时间,它永远不会返回,只是崩溃与segfault)。 **

我设法搞清楚了 – 毕竟问题在于insert()函数。 我重写了它(以及相关的其余代码)来使用常规指针而不是指向指针的指针:

 BST* insert(BST* root, char *key) { // If branch is null, call createBranch // to make one, then return it if (root == NULL) { return createBranch(key); } // Otherwise, check whether the key // already exists in the tree if (!existsInTree(root, key, cflag)) { // If it doesn't, check whether // the case sensitivity flag is set if (cflag) { // If it is, use the case-sensitive // string comparison function to // decide where to insert the key if (caseSenStrCmp(root->key, key)) { // If the key provided is greater // than the string stored at the // current branch, insert into // right child root->right = insert(root->right, key); } else { // Otherwise, insert into left child root->left = insert(root->left, key); } } // If it isn't, use the case-INsensitive string // comparison function to decide where to insert else { // Same logic as before. If the key // provided is greater, insert into // current branch's right child if (caseInsenStrCmp(root->key, key)) { root->right = insert(root->right, key); } // Otherwise, insert into the left child else { root->left = insert(root ->left, key); } } } // Return the root pointer return root; } 

这立即解决了无限递归/ seg故障问题。 它确实揭示了一些其他的小的语义错误(其中大部分都可能是因为我拼命试图解决这个问题而不重写插入函数而沮丧),但我一直在照顾那些。

我现在遇到了一个新问题(虽然可能比这个更简单),我将为它做一个单独的线程,因为它与分段错误无关。