在使用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
之后在程序结束时调用(因为它在工作代码中)时,不会引发双重自由错误?
答案 0 :(得分:3)
当您释放该名称时,指针仍指向列表中最近添加的名称部分。它没有指向列表中的早期项目,因为指针由结构元素正确管理,并且名称被移动到指向不同的值(最新添加)。这就是名称为free()d。
的原因在释放struct元素本身之前,你也没有正确释放每个struct元素(即name)中的变量。我个人建议获取valgrind(linux)或this(windows)并通过它运行程序。
答案 1 :(得分:2)
据我了解,如果在打印列表之前调用free(theName)
,则释放最后一个列表节点指向的内存。此外,我对使用realloc
分配新内存持怀疑态度;打印列表时,您可能会读取包含预期数据的内存,但已由realloc
取消分配。
请注意,realloc允许移动内存块的起始地址,这意味着即使写入realloc
返回的地址,旧内容仍可能存在。