我正在编写一个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;
答案 0 :(得分:3)
以下是此代码可能出现错误的原因:
确保调用代码:
查看粘贴的调用代码...
调用代码不分配节点;它是一个未初始化的指针,这意味着它指向内存中的随机位置。
由于命名而误读了该代码。传递下来的孩子是一个未初始化的指针,但这不会在演示的代码中出现段错误,因为它永远不会被解除引用。
第一次输入{
时,它应该可以正常工作。第二次它应该是段错误,因为this = node
将this
设置为未初始化的指针。
答案 1 :(得分: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
的内存。相反,您以前分配的内存足以容纳struct vsm_node*
,这不够大。一个好的经验法则是malloc
调用的右侧应该包含比左侧少一个间接级别。也就是说,如果您分配的变量的类型为T**
,则sizeof
内的malloc
调用应引用类型T*
,依此类推任何数字*
个字符。
(3)您的结构定义如下:
struct vsm_node {
int numchildren;
struct vsm_attribute *attrs [36];
struct vsm_node *children[8];
};
这意味着当您分配struct vsm_node
对象时,无论是使用malloc
还是仅通过声明struct vsm_node
类型的变量,它已经为您的两个数组都分配了足够的内存指针。分配更多内存不仅是不必要的,而且是浪费的。它还可能导致程序的行为与仅使用数组分配额外内存时的行为不同。
答案 2 :(得分:0)
您正在为
中的节点分配内存vsm_initnode(struct vsm_node *node)
此处节点是一个局部变量,当您的vsm_initnode()完成其执行并返回其调用模块时,您为节点执行的分配将丢失。排序悬空指针问题。为了保留分配,您必须使用双指针或返回内存地址。
下面你可以找到(双指针和返回内存地址)实现。
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
}