0

我正在编写一个 C 应用程序,其中涉及将文本文件(以称为VSM的格式,因此以下名称)解析为树结构。该格式的设计者称其为标记树。每个节点都有一些键值对(或属性)和子节点。

以下是有问题的结构和函数:

vsm.h:

struct vsm_node {
    int                   numchildren;
    struct vsm_attribute *attrs   [36];
    struct vsm_node      *children[8];
};

void vsm_addchild(struct vsm_node *node, struct vsm_node *child);

vsm.c:

#include "vsm.h"

void vsm_addchild(struct vsm_node *node, struct vsm_node *child)
{
    node->children[node->numchildren] = child;
    ++(node->numchildren);
}

为什么当我调用vsm_addchild它时会产生段错误?

对不起,如果这是一个愚蠢的问题,但我真的很讨厌纯 C。特别是如果它与指针和内存管理有关。


编辑以包含拨打电话的代码:

#include <stdio.h>
#include "vsm.h"

void vsm_parse(struct vsm_node *tree, FILE *fp, char *name)
{
    struct vsm_node *this     = tree;

    int ch;
    while ((ch = fgetc(fp)) != 0) {
        ...
        else if (ch == '{') {
            struct vsm_node *node;
            vsm_initnode(node);
            vsm_addchild(this, node);
            this = node;
            ...
        ...

编辑:添加了vsm_initnode功能,但我不知道我是否做得对。

void vsm_initnode(struct vsm_node *node)
{
    node              = malloc(     sizeof(struct vsm_node *));
    node->attrs       = malloc(36 * sizeof(struct vsm_attribute *));
    node->children    = malloc( 8 * sizeof(struct vsm_node *));
    node->numchildren = 0;

    int i;
    for (i = 0; i < 36; ++i)
        node->attrs[i] = NULL; /* unnecessary? */
    for (i = 0; i  < 8; ++i)
        node->children[i] = NULL;
}

void vsm_addchild(struct vsm_node *node, struct vsm_node *child)
{
    node->children[node->numchildren] = child;
    ++(node->numchildren);
}

gdb 输出:

Program received signal SIGSEGV, Segmentation fault.
vsm_addchild (node=0x28, child=0x7541612d <msvcrt!_atodbl_l+2294>) at vsm.c:62
62              node->children[node->numchildren] = child;
4

3 回答 3

3

以下是此代码可能出现段错误的原因:

  1. node 为 NULL、未初始化、指向已释放的内存,或
  2. node->children 为 NULL,未初始化,指向已释放的内存,或
  3. node->numchildren 大于 node->children 的分配大小

确保调用代码:

  1. 分配节点
  2. 分配节点->子节点
  3. 设置节点-> numchildren
  4. 为节点传递了正确的值

查看粘贴的调用代码...

调用代码不分配节点;它是一个未初始化的指针,这意味着它指向内存中的一个随机位置。

由于命名而误读了该代码。传递下来的孩子是一个未初始化的指针,但这不会在演示的代码中出现段错误,因为它从未被取消引用。

第一次{进入,它应该可以正常工作。第二次它应该出现段错误,因为this = node设置this为未初始化的指针。

于 2013-09-02T03:07:11.820 回答
1

您正在错误地初始化节点指针。这意味着node您传递的变量无效并且随机指向内存,几乎可以肯定指向您的进程不拥有的内存,从而导致分段错误。试试这个:

// Change (1): new return type, parameter removed
struct vsm_node* vsm_initnode()
{
    struct vsm_node* node;
    node              = malloc(     sizeof(struct vsm_node)); // Change (2)

    // The following lines are unnecessary - change (3)
    //node->attrs       = malloc(36 * sizeof(struct vsm_attribute *));
    // node->children    = malloc( 8 * sizeof(struct vsm_node *));

    node->numchildren = 0;

    int i;
    for (i = 0; i < 36; ++i)
        node->attrs[i] = NULL; // unnecessary but good practice
    for (i = 0; i  < 8; ++i)
        node->children[i] = NULL;

    return node;
}

void vsm_addchild(struct vsm_node *node, struct vsm_node *child)
{
    node->children[node->numchildren] = child;
    ++(node->numchildren);
}

每个错误的解释:

(1) - 您最初struct vsm_node*是作为参数传递给函数的。这允许您修改指针所指的任何值。但是,虽然您可以修改指针本身(就像您通过调用一样malloc),但这些更改不会反映在调用者中。您所有的内存分配和初始化都被浪费了。

(2) - 类型的指针struct vsm_node*必须指向足够大的内存以容纳struct vsm_node. 相反,您之前分配了足够的内存来保存 a struct vsm_node*,这还不够大。一个好的经验法则是malloc调用的右侧应该比左侧少一个间接级别。也就是说,如果您要分配给的变量具有 type T**,那么sizeof内部的调用malloc应该引用 type T*,等等任意数量的*字符。

(3) 你的结构体定义如下:

struct vsm_node {
    int                   numchildren;
    struct vsm_attribute *attrs   [36];
    struct vsm_node      *children[8];
};

这意味着当您分配一个struct vsm_node对象时,无论是通过使用malloc还是通过简单地声明 type 变量struct vsm_node,它已经为您的两个指针数组提供了足够的内存。分配更多的内存不仅没有必要而且很浪费。它还可能导致您的程序的行为与仅使用分配额外内存的数组不同。

于 2013-09-02T04:14:16.967 回答
0

您正在为节点分配内存

vsm_initnode(struct vsm_node *node) 

这里node是一个局部变量,当你的 vsm_initnode() 完成执行并返回到它的调用模块时,你为 node 所做的分配会丢失。悬空指针问题的排序。为了保留分配,您必须处理双指针返回内存地址

您可以在下面找到(双指针和返回内存地址)实现。

void vsm_parse(struct vsm_node *tree, FILE *fp, char *name)
{
    struct vsm_node *this     = tree;
    int ch;
    while ((ch = fgetc(fp)) != 0) {
        ...
        else if (ch == '{') {
           // Creating a object pointer.
           struct vsm_node *node; 

           /**********************************/
          /* Double Pointer Implementation  */
         /**********************************/
          vsm_initnode_double_pointer(&node); 


          /******************************************/
         /*  Return Memory Address Implementation  */
        /******************************************/
         node=vsm_initnode_return_address(); 

         vsm_addchild(this, node);
         this = node;
        ...
    ...

// Double Pointer Implementation 
void vsm_initnode_double_pointer(struct vsm_node **node)
{
    *node=malloc(sizeof(struct vsm_node *));
    // No need to allocate memory for attrs, children because it has been declared as array. You can directly store the memory location values in it.
    (*node)->numchildren = 0;
    int i;
    for (i = 0; i < 36; ++i)
       (*node)->attrs[i] = NULL;
    for (i = 0; i  < 8; ++i)
       (*node)->children[i] = NULL;
}

// Return Memory Address Implementation
struct vsm_node * vsm_initnode_return_address()
{
    struct vsm_node *temp=malloc(sizeof(struct vsm_node *));
    // No need to allocate memory for attrs, children because it has been declared as array. You can directly store the memory location values in it.
    temp->numchildren = 0;
    int i;
    for (i = 0; i < 36; ++i)
       temp->attrs[i] = NULL; 
    for (i = 0; i  < 8; ++i)
       temp->children[i] = NULL;
return temp
}
于 2013-09-02T04:16:09.490 回答