在使用C语言中的链表时,我注意到了这种行为,但我不理解。下面的示例代码说明了这种情况:声明一个简单的列表,并填充包含`*char`名称的节点。 `theName`字符串是通过将命令行中给定的每个参数连接起来生成的,因此charNum比`argv[i]`大2以容纳`_`和`\0`。`argv`的每个元素都会生成一个节点,并在`main`函数中的`for`循环中添加到列表中。
然而,当我修改这段代码并在打印列表之前调用
解放
感谢您所有的评论和答案。我修改了代码以删除 malloc 的结果强制转换,添加了 node->name 的释放,并将“malloc -> 多次 realloc -> free”更改为名称的“多次 malloc -> 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 = (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)
破坏了最后一个节点的名称,我会猜测 realloc
也会做同样的事情,所有列表中的节点都将具有空白名称。
感谢您所有的评论和答案。我修改了代码以删除 malloc 的结果强制转换,添加了 node->name 的释放,并将“malloc -> 多次 realloc -> free”更改为名称的“多次 malloc -> 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.
然而,当我在nprint(list);
之前加上free(theName);
:
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
当我在
nprint(list);
之后并在nfree(list);
之前加上free(theName);
时: 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,那么当程序在nfree(list);
之后调用free(theName);
时,为什么不会引发双重释放错误(就像在工作代码中一样)?
realloc(NULL, sizeof (char)*charNum);
等同于malloc(sizeof (char)*charNum);
。 - alkchar* theName = (char*) malloc(0);
可能会泄漏内存,这取决于 libc 的实现。 - alkmalloc/calloc/realloc
的结果进行强制类型转换,也不建议这样做。 - alk