【问题标题】:segmentation fault (core dumped) in binary search tree二叉搜索树中的分段错误(核心转储)
【发布时间】:2018-05-20 17:36:13
【问题描述】:

我正在尝试实现一个 r&b 树,但首先我想要一个简单的二叉树(它不会在其叶子上保存内容),然后实现 r&b 属性。问题是我遇到了无法解释的分段错误。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>

typedef struct node
{
    unsigned long int val;
    bool black;
    struct node* parent;
    struct node* lchild;
    struct node* rchild;
}mynode;

mynode* createNode(unsigned long int ival, mynode* father);
mynode* createLeaf(unsigned long int ival, mynode* father);
mynode* search (unsigned long int ival, mynode *root);
void insert ( unsigned long int ival, mynode *root);


int main()
{
    mynode root;
    mynode *rootptr;
    mynode *leafptr;
    FILE *fp;
    int ch;
    unsigned long long lines=0, i=0;
    unsigned long *myArr;
    unsigned long int ival;

   fp = fopen("integers.txt","r");
   if(fp == NULL)
   {
      printf("Error in opening file.");
      return(-1);
   }

    while(!feof(fp))
    {
    ch = fgetc(fp);
    if(ch == '\n')
    {
        lines++;
    }
    }
    lines++;
    printf("lines = %lu", lines);
    myArr = (unsigned long*)calloc(lines, sizeof(unsigned long));

    fseek(fp, 0, SEEK_SET);
    while(!feof(fp))
    {
          fscanf(fp, "%lu,", &myArr[i] ); // des ta pos k giati tou input.
          i++;
    }
    fclose(fp);

    root.val = myArr[0];
    root.parent = NULL;
    root.lchild = NULL;
    root.rchild = NULL;
    root.black = true;
    rootptr = &root;
    leafptr = createLeaf(rootptr->val, rootptr);
    rootptr->lchild = leafptr;
    leafptr = createLeaf(rootptr->val, rootptr);
    rootptr->rchild = leafptr;
    for(i=1; i<lines; i++)
    {
        ival = myArr[i];
        insert(ival, rootptr);
    }

    return 0;
}

mynode* createNode(unsigned long int ival, mynode* father)
{
  mynode* nodeptr;
  mynode node;
  nodeptr = &node;
  nodeptr->val = ival;
  nodeptr->lchild = NULL;
  nodeptr->rchild = NULL;
  nodeptr->parent = father;
  nodeptr->black = true;
  return nodeptr;
}

mynode* createLeaf(unsigned long int ival, mynode* father)
{
  mynode* nodeptr;
  mynode leaf;
  nodeptr = &leaf;
  nodeptr->val = ival;
  nodeptr->lchild = NULL;
  nodeptr->rchild = NULL;
  nodeptr->parent = father;
  nodeptr->black = true;
  return nodeptr;
}

mynode* search (unsigned long int ival, mynode *rootptr)
{
    mynode* myptr;

    myptr = rootptr;

    while ( ( (myptr->lchild) != NULL) && ( (myptr->rchild) != NULL))
    {
        if ( ival < myptr->val)
        {
            myptr = myptr->lchild;
        }
        else
        {
            myptr = myptr->rchild;
        }
    }
    return myptr;
    }

void insert (unsigned long int ival, mynode *root)
{
    mynode * current;
    mynode * leafptr;
    mynode * father;
    unsigned long int max, min;
    unsigned long int num;

    current = search(ival, root);
    num = current->val;
    if((current->val) == ival)
    {
        return ;
    }
    else
    {
        if(ival>(current->val))
        {
            max = ival;
            min = current->val;
        }
        else
        {
            max = current->val;
            min = ival;
        }
        father = current->parent;
        current = createNode(min, father);
        if(num == (father->lchild)->val)
        {
            father->lchild = current;
        }
        else
        {
            father->rchild = current;
        }
        leafptr = createLeaf(min, current);
        current->lchild = leafptr;
        leafptr = createLeaf(max, current);
        current->rchild = leafptr;
       return ;
    }
}

我打开一个文件。计算行数,因为我知道每一行都有一个数字。使用上述信息创建一个数组。然后我创建根和它的 2 片叶子。然后我将数组的其余部分插入(在那里我得到分段错误)到我的数据结构中。我认为问题在于功能。

Here is the text file with numbers.

【问题讨论】:

  • 这应该会发出警告:createNode()createLeaf() 返回一个指向本地对象的指针(node 的地址),一旦您从函数返回,这些指针就无效了。

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


【解决方案1】:
mynode* nodeptr;
mynode node;
nodeptr = &node;

这里node 位于内存中的堆栈上,将在函数退出时回收。您正在返回一个指向您不拥有的内存的指针!你会想要使用函数malloc(),像这样:

mynode* nodeptr = malloc(sizeof(mynode));

这将在nodeptr 指向的堆上分配内存。此内存将不会在函数退出时被回收。要回收此内存,您需要致电 free()

【讨论】:

    【解决方案2】:

    太多的错误和问题无法彻底解决。

    让我们看一个适当的例子,它带有错误检查,以 Graphviz DOT 格式输出生成的二叉搜索树。

    首先,您需要将指针保留在结构的开头,因为它们最常用,并且需要正确对齐。 (如果你不把最大的成员放在第一位,编译器可能会在你的结构中插入填充,浪费内存。不,编译器不允许在 C 中重新排序结构成员,所以它不能为你这样做。)

    #include <stdlib.h>
    #include <stdio.h>
    
    struct node {
        struct node *parent;
        struct node *left;
        struct node *right;
        double       value;
    };
    

    接下来,我们需要一个用于创建新节点的函数。检查malloc() 是否返回NULL 是个好主意,如果是,则中止程序:

    struct node *new_node(const double value)
    {
        struct node *n;
    
        n = malloc(sizeof (struct node));
        if (!n) {
            fprintf(stderr, "Out of memory.\n");
            exit(EXIT_FAILURE);
        }
    
        n->parent = NULL;
        n->left = NULL;
        n->right = NULL;
        n->value = value;
    
        return n;
    }
    

    接下来,您需要一个将节点插入树的函数。树本身只是其根成员的句柄,由于新节点可以是新根,我们需要传递一个指向根元素的指针:

    void insert_node(struct node **root, struct node *leaf)
    {
        struct node *parent;
    
        /* Make sure we have a pointer to the root pointer, and a leaf node. */
        if (!root) {
            fprintf(stderr, "insert_node(): root == NULL!\n");
            exit(EXIT_FAILURE);
        }
        if (!leaf) {
            fprintf(stderr, "insert_node(): leaf == NULL!\n");
            exit(EXIT_FAILURE);
        }
    
        /* Make sure leaf pointers are all NULL. */
        leaf->parent = NULL;
        leaf->left = NULL;
        leaf->right = NULL;
    

    上面的代码只是完整性检查,但为了完整性,我想包含它们。无论如何,如果树为空,根指针指向一个 NULL 指针,即*root == NULL。在这种情况下,leaf 是树中的新(唯一)节点:

        /* Is this a new root node? */
        if (!*root) {
            /* Yes. */
            *root = leaf;
            return;
        }
    

    否则,我们需要下降到树中。我决定 left 的意思是“小于或等于”,因为它很容易记住。如果我们向左走,并且父节点的左节点为空,那么我们将放置新的叶节点。类似地,如果我们往右走,而父节点的右节点是空的,我们把叶子放在那里。否则我们下降。

        /* Find the parent node where leaf belongs. */
        parent = *root;
        while (1)
            if (parent->value >= leaf->value) {
                if (parent->left) {
                    parent = parent->left;
                    continue;
                }
    
                /* This belongs at parent->left. */
                parent->left = leaf;
                leaf->parent = parent;
                return;
    
            } else {
                if (parent->right) {
                    parent = parent->right;
                    continue;
                }
    
                /* This belongs at parent->right. */
                parent->right = leaf;
                leaf->parent = parent;
                return;
            }
    }
    

    要遍历树,并用 DOT 语言打印其结构,只需要两个函数:一个打印节点的递归函数,一个打印样板文件的 main 函数,并调用递归函数。我使用%p或节点指针值作为节点标识符,因为它简单可靠:

    static void dot_recurse(FILE *out, struct node *one)
    {
        fprintf(out, "    \"%p\" [ label=\"%.3f\" ];\n", (void *)one, one->value);
    
        if (one->parent)
            fprintf(out, "    \"%p\" -> \"%p\";\n", (void *)one, (void *)(one->parent));
    
        if (one->left) {
            dot_recurse(out, one->left);
            fprintf(out, "    \"%p\" -> \"%p\" [ label=\"≤\" ];\n", (void *)one, (void *)(one->left));
        }
    
        if (one->right) {
            dot_recurse(out, one->right);
            fprintf(out, "    \"%p\" -> \"%p\" [ label=\">\" ];\n", (void *)one, (void *)(one->right));
        }
    }
    
    void dot(FILE *out, struct node *tree)
    {
        if (out && tree) {
            fprintf(out, "digraph {\n");
            dot_recurse(out, tree);
            fprintf(out, "}\n");
        }
    }
    

    在上面,指向父级的箭头将未标记,指向左子级的箭头将标记为,而指向右子级的箭头将在箭头中间附近标记为&gt;

    请注意,对于dot(),第一个参数是文件将被发送到的流,第二个参数是指向根节点的指针。因为我们不修改树,指向根节点的指针就足够了;我们这里不需要指向根节点的指针。

    最后,我们需要从一个流(这里是标准输入)中读取值,并从每个解析的值构造一个树节点,并将它们插入到树中。绝对没有理由读取文件两次,因为值的数量无关紧要:我们可以简单地读取值,直到我们不能!

    int main(void)
    {
        struct node *tree = NULL;
        double       value;
    
        while (scanf(" %lf", &value) == 1)
            insert_node(&tree, new_node(value));
    
        /* Dump tree in DOT format. Use Graphviz to visualize the output. */
        dot(stdout, tree);
    
        return EXIT_SUCCESS;
    }
    

    main() 的后半部分只是将 DOT 格式的树转储到标准输出,然后退出(成功)。没有必要在退出之前释放动态分配的内存,因为操作系统会自动执行此操作。

    假设我们有一个输入文件in.txt,其中包含

    4.695 5.108 3.518 4.698 8.496
    7.956 9.435 5.341 0.583 7.074
    7.661 5.966 0.557 4.332 1.436
    6.170 7.936 4.630 7.694 0.220
    

    我们执行了我们的程序,将该文件传送到其标准输入,并将输出传送到out.dot。 (在 Linux、Mac OS 和 BSD 中,在将上述 C 源代码编译为当前目录中名为 binary 的可执行文件之后,这将只是 ./binary &lt; in.txt &gt; out.dot。)

    out.dot 将包含

    digraph {
        "0x13dd020" [ label="4.695" ];
        "0x13dd080" [ label="3.518" ];
        "0x13dd080" -> "0x13dd020";
        "0x13dd1a0" [ label="0.583" ];
        "0x13dd1a0" -> "0x13dd080";
        "0x13dd260" [ label="0.557" ];
        "0x13dd260" -> "0x13dd1a0";
        "0x13dd3b0" [ label="0.220" ];
        "0x13dd3b0" -> "0x13dd260";
        "0x13dd260" -> "0x13dd3b0" [ label="≤" ];
        "0x13dd1a0" -> "0x13dd260" [ label="≤" ];
        "0x13dd2c0" [ label="1.436" ];
        "0x13dd2c0" -> "0x13dd1a0";
        "0x13dd1a0" -> "0x13dd2c0" [ label=">" ];
        "0x13dd080" -> "0x13dd1a0" [ label="≤" ];
        "0x13dd290" [ label="4.332" ];
        "0x13dd290" -> "0x13dd080";
        "0x13dd350" [ label="4.630" ];
        "0x13dd350" -> "0x13dd290";
        "0x13dd290" -> "0x13dd350" [ label=">" ];
        "0x13dd080" -> "0x13dd290" [ label=">" ];
        "0x13dd020" -> "0x13dd080" [ label="≤" ];
        "0x13dd050" [ label="5.108" ];
        "0x13dd050" -> "0x13dd020";
        "0x13dd0b0" [ label="4.698" ];
        "0x13dd0b0" -> "0x13dd050";
        "0x13dd050" -> "0x13dd0b0" [ label="≤" ];
        "0x13dd0e0" [ label="8.496" ];
        "0x13dd0e0" -> "0x13dd050";
        "0x13dd110" [ label="7.956" ];
        "0x13dd110" -> "0x13dd0e0";
        "0x13dd170" [ label="5.341" ];
        "0x13dd170" -> "0x13dd110";
        "0x13dd1d0" [ label="7.074" ];
        "0x13dd1d0" -> "0x13dd170";
        "0x13dd230" [ label="5.966" ];
        "0x13dd230" -> "0x13dd1d0";
        "0x13dd2f0" [ label="6.170" ];
        "0x13dd2f0" -> "0x13dd230";
        "0x13dd230" -> "0x13dd2f0" [ label=">" ];
        "0x13dd1d0" -> "0x13dd230" [ label="≤" ];
        "0x13dd200" [ label="7.661" ];
        "0x13dd200" -> "0x13dd1d0";
        "0x13dd320" [ label="7.936" ];
        "0x13dd320" -> "0x13dd200";
        "0x13dd380" [ label="7.694" ];
        "0x13dd380" -> "0x13dd320";
        "0x13dd320" -> "0x13dd380" [ label="≤" ];
        "0x13dd200" -> "0x13dd320" [ label=">" ];
        "0x13dd1d0" -> "0x13dd200" [ label=">" ];
        "0x13dd170" -> "0x13dd1d0" [ label=">" ];
        "0x13dd110" -> "0x13dd170" [ label="≤" ];
        "0x13dd0e0" -> "0x13dd110" [ label="≤" ];
        "0x13dd140" [ label="9.435" ];
        "0x13dd140" -> "0x13dd0e0";
        "0x13dd0e0" -> "0x13dd140" [ label=">" ];
        "0x13dd050" -> "0x13dd0e0" [ label=">" ];
        "0x13dd020" -> "0x13dd050" [ label=">" ];
    }
    

    如果可视化(使用例如dot -Tsvg out.dot &gt; out.svg),将如下所示:

    如您所见,每个左孩子都等于或小于其父母,每个右孩子都大于其父母。像这样使用 DOT 语言输出也是调试树函数的绝佳方法;您甚至可以使用节点形状(例如椭圆形或矩形)来指示红色/黑色 - 或使用实际颜色。 DOT 属性列表为here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-06
      相关资源
      最近更新 更多