请在C中解释这个* char malloc / realloc / free行为

时间:2014-03-20 14:50:25

标签: c pointers malloc free realloc

在使用C中的链接列表时,我注意到了这种我不理解的行为。下面的示例代码说明了声明一个简单列表并填充包含*char名称的节点的情况。通过theName附加命令行中给出的每个参数生成_字符串,因此charNum大于argv[i] 2,以容纳_和{{1} }。每个\0元素都会生成一个节点,该节点将添加到argv函数的for循环中的列表中。

main

上面的代码可以正常运行:

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

struct node {
  char* name;
  struct node* next;
};

struct node*
nalloc(char* name)
{
  struct node* n = (struct node*) malloc(sizeof(struct node));
  if (n)
  {
    n->name = name;
    n->next = NULL;
  }
  return n;
}

struct node*
nadd(struct node* head, char* name)
{
  struct node* new = nalloc(name);
  if (new == NULL) return head;
  new->next = head;
  return new;
}

void
nprint(struct node* head)
{
  struct node* n = NULL;
  printf("List start: \n");
  for(n = head; n; n=n->next)
  {
    printf("  Node name: %s, next node: %p\n", n->name, n->next);
  }
  printf("List end. \n");
}

void
nfree(struct node* head)
{
  struct node* n = NULL;
  printf("Freeing up the list: \n");
  while (head)
  {
    n = head;
    printf("  Freeing: %s\n", head->name);
    head = head->next;
    free(n);
  }
  printf("Done.\n");
}

int
main(int argc, char** argv)
{
  struct node* list = NULL;
  char* theName = (char*) malloc(0);
  int i, charNum;
  for (i=0; i < argc; i++)
  {
    charNum = strlen(argv[i]) + 2;
    theName = (char*) realloc(NULL, sizeof (char)*charNum);
    snprintf(theName, charNum, "%s_", argv[i]);
    list = nadd(list, theName);
  }
  nprint(list);
  nfree(list);
  free(theName);
  return 0;
}

但是,当我修改此代码并在打印列表前调用$ ./a.out one two three List start: Node name: three_, next node: 0x1dae0d0 Node name: two_, next node: 0x1dae090 Node name: one_, next node: 0x1dae050 Node name: ./a.out_, next node: (nil) List end. Freeing up the list: Freeing: three_ Freeing: two_ Freeing: one_ Freeing: ./a.out_ Done. 时:

free(theName)

缺少最后一个列表项的名称:

  ...
  free(theName);
  nprint(list);
  nfree(list);
  return 0;
  ...

因此,释放$ ./a.out one two three List start: Node name: , next node: 0x3f270d0 Node name: two_, next node: 0x3f27090 Node name: one_, next node: 0x3f27050 Node name: ./a.out_, next node: (nil) List end. Freeing up the list: Freeing: Freeing: two_ Freeing: one_ Freeing: ./a.out_ Done. 指针会影响使用它作为名称的列表节点,但早期的theName如何不影响其他节点?如果realloc打破了最后一个节点的名称,我猜测free(theName)会做同样的事情,列表中的所有节点都会有空白名称。


谢谢大家的意见和回答。我修改了代码以删除malloc结果的转换,添加了node-&gt; name的释放并更改了'malloc - &gt;多个reallocs - &gt;免费'到'多个mallocs - &gt;这个名字是免费的。所以这是新代码:

realloc

以上工作符合预期:

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

struct node {
  char* name;
  struct node* next;
};

struct node*
nalloc(char* name)
{
  struct node* n = malloc(sizeof(struct node));
  if (n)
  {
    n->name = name;
    n->next = NULL;
  }
  return n;
}

struct node*
nadd(struct node* head, char* name)
{
  struct node* new = nalloc(name);
  if (new == NULL) return head;
  new->next = head;
  return new;
}

void
nprint(struct node* head)
{
  struct node* n = NULL;
  printf("List start: \n");
  for(n = head; n; n=n->next)
  {
    printf("  Node name: %s, next node: %p\n", n->name, n->next);
  }
  printf("List end. \n");
}

void
nfree(struct node* head)
{
  struct node* n = NULL;
  printf("Freeing up the list: \n");
  while (head)
  {
    n = head;
    printf("  Freeing: %s\n", head->name);
    head = head->next;
    free(n->name);
    free(n);
  }
  printf("Done.\n");
}

int
main(int argc, char** argv)
{
  struct node* list = NULL;
  char* theName;
  int i, charNum;
  for (i=0; i < argc; i++)
  {
    charNum = strlen(argv[i]) + 2;
    theName = malloc(sizeof (char)*charNum);
    snprintf(theName, charNum, "%s_", argv[i]);
    list = nadd(list, theName);
  }
  nprint(list);
  nfree(list);
  free(theName);
  return 0;
}

但是当我在$ ./a.out one two three List start: Node name: three_, next node: 0x1826c0b0 Node name: two_, next node: 0x1826c070 Node name: one_, next node: 0x1826c030 Node name: ./a.out_, next node: (nil) List end. Freeing up the list: Freeing: three_ Freeing: two_ Freeing: one_ Freeing: ./a.out_ Done. 之前放置free(theName);时:

nprint(list);

在输出中,最后一个节点的名称丢失, free(theName); nprint(list); nfree(list); return 0; 抛出错误:

nfree(list);

当我在$ ./a.out one two three List start: Node name: , next node: 0x1cf3e0b0 Node name: two_, next node: 0x1cf3e070 Node name: one_, next node: 0x1cf3e030 Node name: ./a.out_, next node: (nil) List end. Freeing up the list: Freeing: *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000001cf3e0d0 *** ======= Backtrace: ========= ... ======= Memory map: ======== ... Aborted 之后和free(theName);之前nprint(list);放置

nfree(list);
在输出中

正确打印所有节点,但 nprint(list); free(theName); nfree(list); return 0; 仍然会抛出错误:

nprint(list);

这引出了另一个问题:我猜测在任何情况下$ ./a.out one two three List start: Node name: three_, next node: 0x19d160b0 Node name: two_, next node: 0x19d16070 Node name: one_, next node: 0x19d16030 Node name: ./a.out_, next node: (nil) List end. Freeing up the list: Freeing: *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000001cf3e0d0 *** ======= Backtrace: ========= ... ======= Memory map: ======== ... Aborted 指向的内存都被释放两次:首先是node-&gt; name,第二个是name,所以怎么来{{1在theName之后在程序结束时调用(因为它在工作代码中)时,不会引发双重自由错误?

2 个答案:

答案 0 :(得分:3)

当您释放该名称时,指针仍指向列表中最近添加的名称部分。它没有指向列表中的早期项目,因为指针由结构元素正确管理,并且名称被移动到指向不同的值(最新添加)。这就是名称为free()d。

的原因

在释放struct元素本身之前,你也没有正确释放每个struct元素(即name)中的变量。我个人建议获取valgrind(linux)或this(windows)并通过它运行程序。

答案 1 :(得分:2)

据我了解,如果在打印列表之前调用free(theName),则释放最后一个列表节点指向的内存。此外,我对使用realloc分配新内存持怀疑态度;打印列表时,您可能会读取包含预期数据的内存,但已由realloc取消分配。

请注意,realloc允许移动内存块的起始地址,这意味着即使写入realloc返回的地址,旧内容仍可能存在。