释放内存会导致分段错误

时间:2018-11-27 07:32:49

标签: c

我一直在尝试使用C语言的结构,指针和内存。 我已经创建了这个结构

 typedef struct {
  int id;
  char *name;
} Object;

这是构造函数

    void object_ctor(Object *o, int id, char *name)
{
    o->id = id;
    o->name = malloc(sizeof(name));
    if(sizeof(o->name)!=sizeof(name))
    {
        o->name=NULL;
    }
    else
    {
        strcpy(o->name, name);
    }
}

这是o1的去壳

  char tmp_name[] = "Hello 1";
Object o1;
object_ctor(&o1, 1, tmp_name);

这里是析构函数

void object_dtor(Object *o)
{
    if(o->name != NULL)
    {
        free(o->name);
        o->name = NULL;
    }

}

打印对象

void print_object(Object *o)
{
    printf("ID: %d, NAME: %s\n", o->id, o->name);
}

通话副本

   Object copy;
   print_object(object_cpy(&copy, &o1));

我正在尝试创建一个结构到另一个结构的副本(我已经构造了它们)。

Object *object_cpy(Object *dst, Object *src)
{
    if(src!=NULL)
    {
      const size_t len_str=strlen(src->name)+1;
      dst->name = malloc(10000000);
      dst->id = src->id;
      strncpy (dst->name, src->name,len_str);
    }

    if (strcmp(dst->name,src->name)!=0)
    {
      dst->name = NULL;
    }

    return dst;
}

但是当我尝试同时释放副本和原始src时,出现了分段错误。我一直试图通过gdb运行它,它说我要释放两次相同的内存,所以我认为复制代码是错误的,但是我不知道在哪里。

这是给我分段错误的代码

    printf("\nCOPY EMPTY\n");
   object_dtor(&copy);
    o1.id = -1;
   free(o1.name);
    o1.name = NULL;
    object_cpy(&copy, &o1);

    print_object(&copy);
    print_object(&o1);

我包括这些库

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

我正在使用std=c99标志进行编译。

2 个答案:

答案 0 :(得分:1)

这里至少有一个问题:

void object_ctor(Object *o, int id, char *name)
{
  o->id = id;
  o->name = malloc(sizeof(name));
  if (sizeof(o->name) != sizeof(name))
  {
    o->name = NULL;
  }
  else
  {
    strcpy(o->name, name);
  }
}

sizeof(name)不是name指向的字符串的长度。您需要strlen(name) + 1(对于NUL终止符为+1)。

您的测试if (sizeof(o->name) != sizeof(name))是毫无意义的,我不确定您要在这里实现什么。

您可能想要这样:

void object_ctor(Object *o, int id, char *name)
{
  o->id = id;
  o->name = malloc(strlen(name) + 1);

  if (o->name != NULL)
    strcpy(o->name, name);
}

object_cpy中存在类似的问题:

  • 毫无意义地使用strncpy
  • 10Mb缓冲区的无意义分配
  • 无意义的测试strcmp(dst->name, src->name)

您可能想要这样:

Object *object_cpy(Object *dst, Object *src)
{
  if (src != NULL)
  {
    const size_t len_str = strlen(src->name) + 1;
    dst->name = malloc(len_str);

    if (dst->name != NULL)
    {
      dst->id = src->id;
      strcpy(dst->name, src->name);
    }
  }

  return dst;
}

通过这些更正,以下代码可以正常工作:

int main()
{
  char tmp_name[] = "Hello 1";
  Object o1, copy;
  object_ctor(&o1, 1, tmp_name);
  object_cpy(&copy, &o1);

  print_object(&copy);
  print_object(&o1);  

  object_dtor(&o1);
  object_dtor(&copy);
}

答案 1 :(得分:0)

如果这不能直接解决您的问题,请告诉我如何组织代码,以避免像您这样的内存问题。

首先,它们全部围绕结构解析。 对于每个结构,如果需要,我都会执行一个“构造函数”和一个“析构函数”。

构造函数的目的仅仅是将结构设置为一致状态。它永远不会失败(这意味着任何可能失败的代码(例如malloc)都不应位于构造函数中)。 析构函数的目的是清理结构。

我喜欢使用的一个小技巧是将构造函数放在宏中,使我能够执行类似“ Object var = OBJET_CONSTRUCTOR”的操作。 当然,这不可能总是,要由您来保持心意。

对于您的代码,可能是:

typedef struct {
    int  id;
    char *name;
} Object;

#define OBJECT_CONSTRUCTOR {.id   = -1,\ \\ Assuming -1 is relevant in your case, like an error code or a bad id value. Otherwise, it's useless.
                            .name = NULL}

void Object_Constructor(Object *self)
{
    Object clean = OBJECT_CONSTRUCTOR;

    *self = clean;
}

void Object_Destructor(Object *self)
{
    free(self->name);
}

我们在这里。 如何使用它很简单:您总是从构造函数开始,而总是以析构函数结束。这就是为什么在析构函数中将char指针“ name”设置为NULL没用的原因,因为构造函数的任何其他函数都不应使用它。

现在,您可以具有“初始化”功能。您可以执行简单的初始化(这是您的构造函数),也可以执行副本初始化等 请记住,该结构已被调用到构造函数中。如果不是的话,那是开发人员的错,您不必计较。

在出现错误的情况下,一种不错的行为是不修改结构。 结构要么成功地进行了彻底修改,要么根本没有修改。 对于可能在很多时候失败的复杂结构,您可以通过在最后“交换”结果来实现。

void Object_Swap(Object *first, Object *second)
{
    Object tmp = OBJECT_CONSTRUCTOR;

    tmp = *fisrt;
    *first = *second;
    *second = tmp;
}

bool Object_InitByPlainList(Object *self, int id, consr char *name)
{
    Object newly          = OBJECT_CONSTRUCTOR;
    bool   returnFunction = false;

    newly.id = id;
    if (!(newly.name = strdup(name))) {
        printf("error : %s : strdup(name) : name='%s', errno='%s'.\n", __func__, name, strerror(errno));
        goto END_FUNCTION;
    }

    // Success !
    Object_Swap(self, &newly);
    returnFunction = true;

    /* GOTO */END_FUNCTION:
    Object_Destructor(&newly);
    return (returnFunction);
}

乍看之下似乎过于复杂,但是该组织使您可以添加更多未来步骤“可能会失败”。

现在,您甚至可以执行以下操作:

bool Object_InitByCopy(Object *dst, Object *src)
{
    return (Object_InitByPlainList(dst, src->id, src->name));
}

您要做的就是在文档中说:

  • 要调用的第一个函数必须是“ Object_Constructor”
  • 在“ Object_Constructor”之后,只能调用“ Object_Init *”功能。
  • 最后一个要调用的函数必须是“ Object_Destructor”

仅此而已。您可以添加任何您想要的“ Object_ *”函数,例如:

void Object_Print(const Object *self)
{
    printf("ID: %d, NAME: %s\n", self->id, self->name);
}

希望这个组织能解决您的记忆问题。

一个例子:

int main(void)
{
    Object test = OBJECT_CONSTRUCTOR;
    Object copy = OBJECT_CONSTRUCTOR;

    if (!Object_InitByPlainList(&test, 1, "Hello World !")) {
        // The function itself has logged why it has fail, so no need to add error printf here
        return (1);
    }

    Object_Print(&test);

    if (!Object_Copy(&copy, &test)) {
        return (1);
    }

    Object_Destructor(&test);
    Object_Destructor(&copy);
    return (0);
}