【发布时间】:2014-04-27 12:21:42
【问题描述】:
在 C 语言中使用链表时,我注意到了我不理解的这种行为。下面的示例代码说明了声明一个简单列表并填充包含*char 名称的节点的情况。 theName 字符串是通过在命令行中给定的每个参数附加_ 生成的,因此charNum 比argv[i] 大2 以容纳_ 和\0。每个argv 元素都会生成一个节点,该节点将添加到main 函数的for 循环中的列表中。
#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 指针会影响使用它作为名称的列表节点,但为什么更早reallocs 没有影响其他节点?如果free(theName) 破坏了最后一个节点的名称,我猜realloc 会这样做,并且列表中的所有节点都将具有空白名称。
感谢大家的 cmets 和回答。我修改了代码以删除 malloc 结果的强制转换,添加了 node->name 的释放并将名称的 'malloc -> multiple reallocs -> free' 更改为 'multiple mallocs -> free'。所以这是新代码:
#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
这在我的脑海中提出了另一个问题:我猜在任何情况下theName 指向的内存都被释放了两次:第一次作为 node->name,第二次作为 theName,那么为什么 free(theName); 不会引发在nfree(list); 之后在程序末尾调用时出现双释放错误(就像在工作代码中一样)?
【问题讨论】:
-
这个
realloc(NULL, sizeof (char)*charNum);等价于malloc(sizeof (char)*charNum);。 -
这个
char* theName = (char*) malloc(0);可能会泄漏内存,具体取决于 libc 实现。 -
在C中没有需要转换
malloc/calloc/realloc的结果也不推荐。
标签: c pointers malloc free realloc