提问人:fdv 提问时间:10/30/2023 最后编辑:Vlad from Moscowfdv 更新时间:11/2/2023 访问量:137
为结构 C 中的指针提供适当的内存
Proper memory free for pointers inside struct C
问:
我有一个包含一个类型的动态数组,这个数组的每个元素都指向两个类型的变量。在主要
我的程序,我通过 malloc 调用为数组和
对于每个元素指针。然后我为元素赋值,检查该值并释放内存。这是代码struct list
struct pair
struct element
struct pair pairs
#include "stdlib.h"
#include "stdio.h"
struct element{
int i;
};
struct pair{
struct element *element1, *element2;
};
struct list{
struct pair *pairs;
};
int main(void) {
// declare variable my_list
struct list my_list;
// set size of pairs array (one for the example)
my_list.pairs = (struct pair*)malloc(sizeof(struct pair));
// set element1 and element2 of pairs[0]
struct element element1 = {.i = 1};
struct element element2 = {.i = 2};
my_list.pairs[0].element1 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element2 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element1 = &element1;
my_list.pairs[0].element2 = &element2;
// check element values
printf("elements are: %d %d\n", my_list.pairs[0].element1->i, my_list.pairs[0].element2->i);
// free memory
free(my_list.pairs);
return 0;
}
我可以毫无问题地编译和运行代码,但是如果我在 valgrind 中运行代码,我会得到以下内存泄漏:
*** valgrind --leak-check=full -s ./a.out
==6676== Memcheck, a memory error detector
==6676== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6676== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==6676== Command: ./a.out
==6676==
elements are: 1 2
==6676==
==6676== HEAP SUMMARY:
==6676== in use at exit: 8 bytes in 2 blocks
==6676== total heap usage: 4 allocs, 2 frees, 1,048 bytes allocated
==6676==
==6676== 4 bytes in 1 blocks are definitely lost in loss record 1 of 2
==6676== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==6676== by 0x1091EE: main (foo.c:27)
==6676==
==6676== 4 bytes in 1 blocks are definitely lost in loss record 2 of 2
==6676== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==6676== by 0x1091FF: main (foo.c:28)
==6676==
==6676== LEAK SUMMARY:
==6676== definitely lost: 8 bytes in 2 blocks
==6676== indirectly lost: 0 bytes in 0 blocks
==6676== possibly lost: 0 bytes in 0 blocks
==6676== still reachable: 0 bytes in 0 blocks
==6676== suppressed: 0 bytes in 0 blocks
==6676==
==6676== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
第 27 行和第 28 行是泄漏的罪魁祸首。如果我尝试添加(在第 36 行之前),我会在 valgrind 中出现以下错误free(my_list.pairs[0].element1);
==6684== 1 errors in context 1 of 3:
==6684== Invalid free() / delete / delete[] / realloc()
==6684== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==6684== by 0x10924F: main (foo.c:36)
==6684== Address 0x1ffefff918 is on thread 1's stack
==6684== in frame #1, created by main (foo.c:16)
所以我的问题是:
- 如何避免内存泄漏并正确释放第 27 行和第 28 行分配的内存?
- 为什么试图释放指针上升错误?
my_list.pairs[0].element1
Invalid free() / delete / delete[] / realloc()
代码已使用 gcc 编译如下: 我的 gcc 版本是gcc -Wall -g foo.c
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
答:
您的内存泄漏在这里:
my_list.pairs[0].element1 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element2 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element1 = &element1; // overwrite of my_list.pairs[0].element1 --> memory leak
my_list.pairs[0].element2 = &element2; // overwrite of my_list.pairs[0].element2 --> memory leak
溶液:
选项 1
删除两行 doing 。malloc
struct element element1 = {.i = 1};
struct element element2 = {.i = 2};
my_list.pairs[0].element1 = &element1;
my_list.pairs[0].element2 = &element2;
选项 2
删除创建和使用局部变量的行。
my_list.pairs[0].element1 = malloc(sizeof(struct element));
my_list.pairs[0].element2 = malloc(sizeof(struct element));
my_list.pairs[0].element1->i = 1;
my_list.pairs[0].element2->i = 2;
....
....
free(my_list.pairs[0].element1);
free(my_list.pairs[0].element2);
注意:在 C 语言中,你不需要强制转换malloc
在这里,您的程序正在泄漏内存:
my_list.pairs[0].element1 = &element1;
my_list.pairs[0].element2 = &element2;
原因是 - 并且持有动态分配的内存引用,在上述分配之后,它们会丢失这些内存引用。因此,导致内存泄漏。my_list.pairs[0].element1
my_list.pairs[0].element2
请注意,在您的代码中,和不是动态分配的对象。它们是在堆栈上创建的对象:element1
element2
struct element element1 = {.i = 1};
struct element element2 = {.i = 2};
这对他们来说无效:free
free(my_list.pairs[0].element1);
free
只能用于由 、 或 分配的指针,否则将导致未定义的行为。malloc()
calloc()
aligned_alloc()
realloc()
要解决此问题,要么不要将内存分配给 and 动态分配,要么动态分配它们,并在完成它们后分配它们。后文建议的实施情况:my_list.pairs[0].element1
my_list.pairs[0].element2
free
struct element * element1 = (struct element*)malloc(sizeof(struct element));
struct element * element2 = (struct element*)malloc(sizeof(struct element));
element1->i = 1;
element2->i = 2;
my_list.pairs[0].element1 = element1;
my_list.pairs[0].element2 = element2;
// check element values
printf("elements are: %d %d\n", my_list.pairs[0].element1->i, my_list.pairs[0].element2->i);
// free memory
free(my_list.pairs[0].element1);
free(my_list.pairs[0].element2);
附加:
遵循良好的编程实践,确保检查 的返回值。malloc()
评论
struct element element1
并且是静态对象,我同意。但是 和 是动态分配的指针。因此,我希望它们应该是一个有效的操作。我在这里遗漏了什么吗?struct element element2
my_list[0].pairs.element1
my_list[0].pairs.element2
free
my_list[0].pairs.element1
my_list[0].pairs.element2
my_list.pairs[0].element1 = &element1;
my_list.pairs[0].element2 = &element2;
这些陈述
my_list.pairs[0].element1 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element2 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element1 = &element1;
my_list.pairs[0].element2 = &element2;
产生内存泄漏。
首先分配内存并将其地址分配给指针,然后使用局部变量和my_list.pairs[0].element1
my_list.pairs[0].element2
element1
element2
// set element1 and element2 of pairs[0]
struct element element1 = {.i = 1};
struct element element2 = {.i = 2};
因此,已分配内存的加分项丢失了。
而不是使用此分配
my_list.pairs[0].element1 = &element1;
my_list.pairs[0].element2 = &element2;
你应该写
*my_list.pairs[0].element1 = element1;
*my_list.pairs[0].element2 = element2;
复制分配的内存的值。element1
element2
要正确释放分配的内存,您需要以相反的顺序释放它。那是
free( my_list.pairs[0].element1 );
free( my_list.pairs[0].element2 );
free( my_list.pairs );
至于你的代码,那么这个声明
// free memory
free(my_list.pairs);
在分配了三个内存盘区时,仅释放第一个分配的内存盘区
my_list.pairs = (struct pair*)malloc(sizeof(struct pair));
//...
my_list.pairs[0].element1 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].element2 = (struct element*)malloc(sizeof(struct element));
在这一点上,关于为什么你有内存泄漏,你有一些很好的答案,但我将在这里留下一个创建这种有用的东西的方法的例子。或忽略。:)
从原始代码:
为了清楚起见,我换了。也。element1
el1
el2
my_list.pairs[0].el1 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].el2 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].el1 = &el1;
my_list.pairs[0].el2 = &el2;
重新排序行后:
my_list.pairs[0].el1 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].el1 = &el1;
my_list.pairs[0].el2 = (struct element*)malloc(sizeof(struct element));
my_list.pairs[0].el2 = &el2;
它清楚地表明,一旦第 2 行和第 4 行运行,从返回的每个区域都将永远消失,因为在每种情况下,该区域的单个指针都被覆盖了。malloc
也
// declare variable my_list
struct list my_list;
也许你可以避免这种类型的评论。声明的作用很清楚。
示例
我们这里有一个货币对列表。可以实现为数组、链表、字典......java 有 Pair
as of Key 和 Value 的东西,C++ std::p air 中有 pair
of first
和 second
作为例子。并且可以是字符串列表的列表。事物的任何 3 级层次结构。
我将向你展示一种编写方法,使用封装,并查看你的对和列表,更像对象。这样做可以更有效率,因为它使用大量的复制和粘贴,并且简单地重用这些其他语言中的内容,并且(也很重要)形成一种语言。struct
层次结构:成对的元素列表
我们将像一个简单的指针数组一样使用固定容量的指针。它可以通过任何其他方式实现,例如使用链表或简单数组。但列表就是这样:一个(可能是空的)事物集合。在这种情况下,成对的东西。因为有什么东西没有区别。这实质上是封装。在这里,请注意,列表不引用,仅作为不透明对象引用。List
List
Element
Pairs
名单List
#ifndef LIST_H
#define LIST_H
#include <stdio.h>
#include "pair.h"
typedef struct
{
size_t size;
size_t limit;
Pair** pair;
} List;
List* make_list(size_t);
List* delete_list(List*);
int insert_list(Pair*, List*);
int show_list(List*, const char*);
#endif // LIST_H
因此,列表中包含其实际大小和容量的值。这些函数在其他语言中称为方法,不引用元素。甚至可以使用通用名称行或 .Pair
Info
Node
make_list
返回指向具有请求容量的新空列表的指针delete_list
销毁列表并返回 。为什么?这样我们就可以删除一个并同时使其指针失效,以确保安全。NULL
List
insert_list
是否符合预期:将记录插入到列表中。在本例中,一个Pair
show_list
在屏幕上显示列表的元素。要保存一些呼叫,可以添加前缀消息。printf
这就像其他地方/语言中的构造函数、析构函数和 toString 或序列化方法。仅使用指针。这更容易。
可能的实现List.c
#include "list.h"
List* make_list(size_t size)
{
List* one = malloc(sizeof(List));
if (one == NULL) return NULL;
one->limit = size;
one->size = 0;
one->pair = (Pair**)malloc(size * sizeof(Pair*));
if (one->pair == NULL)
{
free(one);
return NULL;
}
return one;
}
List* delete_list(List* L)
{
if (L == NULL) return NULL;
fprintf(
stderr, "\n deleting %llu elements list\n",
L->size);
for (size_t i = 0; i < L->size; i += 1)
delete_pair(L->pair[i]);
free(L);
return NULL;
}
int insert_list(Pair* p, List* l)
{
if (p == NULL) return -1;
if (l == NULL) return -2;
if (l->size == l->limit) return -3;
Pair* pair = make_pair(p->first, p->second);
l->pair[l->size] = pair;
l->size += 1;
return 0;
}
int show_list(List* L, const char* msg)
{
if (L == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf(
"[%llu/%llu] element(s) in list\n", L->size,
L->limit);
for (size_t i = 0; i < L->size; i += 1)
show_pair(L->pair[i], NULL);
printf("\n");
return 0;
}
这对Pair.h
#ifndef PAIR_H
#define PAIR_H
#include <stdio.h>
#include <stdlib.h>
#include "el1.h"
typedef struct
{
Element* first;
Element* second;
} Pair;
Pair* delete_pair(Pair*);
Pair* make_pair(Element*, Element*);
int show_pair(Pair*, const char*);
#endif // PAIR_H
这里我们有成对的命名:元素是 和 。Java 使用 AND 来表示键和值。C++
first
second
K
V
make_pair
返回带有提供的元素的 A。Pair
show_pair
按预期执行。delete_pair
释放一对并返回 。NULL
这些只是复制和改编的。仅使用指针。List
可能的实现Pair.c
#include "pair.h"
Pair* delete_pair(Pair* gone)
{
if (gone == NULL) return NULL;
delete_el(gone->first);
delete_el(gone->second);
free(gone);
return NULL;
}
Pair* make_pair(Element* a, Element* b)
{
if (a == NULL) return NULL;
if (b == NULL) return NULL;
Pair* one = malloc(sizeof(Pair));
if (one == NULL) return NULL;
one->first = copy_el(a);
one->second = copy_el(b);
return one;
}
int show_pair(Pair* p, const char* msg)
{
if (p == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf(" [");
show_el(p->first, NULL);
printf(",");
show_el(p->second, NULL);
printf("]\n");
return 0;
}
元素,终于
这是定制内容的地方。我将使用 2 个:Element
typedef struct
{
int i;
} Element;
和
typedef struct
{
char* name;
int code;
} Element;
第一个与问题相同。第二个只是为了说明一个可以分配内存,里面可以有指针,所以我们不能只把它的值复制到一个里面。Element
Pair
因此,为了处理此类内容,用户需要提供方法。这样做---并且---像其他语言一样从一个程序到另一个程序保持不变。 具有标头。Pair
Pair
List
pair.h
#include
Element
这就是我们在提供 compare 函数时所做的,因此代码可以对任何内容进行排序。qsort
引用可以用其他更聪明的方式完成,但这里只是一个玩具,展示如何快速构建这些东西,所以使用了 an。Element
#include
元素
所需的函数
Element* copy_el(Element*);
Element* delete_el(Element*);
int show_el(Element*, const char*);
它与其他“类”几乎相同:
copy_el
是复制构造函数,这就是我们在 a 中插入一个元素所需要的: 它返回元素副本的地址,因此它归列表所有,可以插入到List
Pair
delete_el
是析构函数,因为 可能有复杂的分配问题。Element
show_el
是一种在屏幕上显示Element
使用int
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
int main(void)
{
Element* one = make_el(42);
Element* other = make_el(-42);
Pair* my_pair = make_pair(one, other);
delete_el(one);
delete_el(other);
show_pair(my_pair, "test_pair is ");
List* my_list = make_list(5); // capacity = 5
show_list(my_list, "\n(list still empty) ");
while (0 == insert_list(my_pair, my_list)) {};
show_list(
my_list, "\n(list now filled with same pair) ");
my_pair = delete_pair(my_pair); // free test_pair
my_list = delete_list(my_list); // free list
return 0;
}
make_el
只是在这些示例中创建 a 的助手。容器只需要知道如何复制一个,而不需要知道如何创建一个。Element
这个程序
- 创建一个
Pair
- 创建一个 5 个元素的 '
List
- 用这对填充列表,因为内容在这里无关紧要。
- 在屏幕上显示列表
free
的everithing
输出
test_pair is [42,-42]
(list still empty) [0/5] element(s) in list
(list now filled with same pair) [5/5] element(s) in list
[42,-42]
[42,-42]
[42,-42]
[42,-42]
[42,-42]
deleting 5 elements list
简单易碎的实现Element
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int i;
} Element;
Element* copy_el(Element*);
Element* delete_el(Element*);
Element* make_el(const int);
int show_el(Element*, const char*);
Element* copy_el(Element* orig)
{
if (orig == NULL) return NULL;
Element* one = malloc(sizeof(Element));
if (one == NULL) return NULL;
one->i = orig->i;
return one;
}
Element* delete_el(Element* orig)
{
if (orig == NULL) return NULL;
free(orig);
return NULL;
}
Element* make_el(const int value)
{
Element* one = malloc(sizeof(Element));
if (one == NULL) return NULL;
one->i = value;
return one;
}
int show_el(Element* el, const char* msg)
{
if (el == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf("%d", el->i);
return 0;
}
同样,这里只是为了示例。容器---任何一个---本身不会创建元素。它只是在需要时复制一个,并在需要时销毁。make_el
使用 分配内存的Element
struct
这是一个内部具有指针并分配内存的示例。它可以以相同的方式使用,因为用户提供了复制构造函数。
main
对于这种情况
这里当然不同,但这只是为了示例。 看不到这一点,也不会创建元素,只是复制它们。当然,用户有一些项目需要管理,并且知道如何去做。只要有效,就可以“列出”。只要作品能做好自己的工作。make_el
List
Pair
List
MagicStuff
copy_el
show_el
List
事实上,指向这些函数的指针可以添加,我们将拥有更接近 C++ 的东西。List
只有前五行是不同的,因为我们需要构建一对元素。代码的其余部分是相同的。更重要的是,代码 和 是相同的。Pair
List
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
int main(void)
{
Element* one = make_el("Stack", 42);
Element* other = make_el("Overflow", -42);
Pair* my_pair = make_pair(one, other);
delete_el(one);
delete_el(other);
show_pair(my_pair, "test_pair is ");
List* my_list = make_list(5); // capacity = 5
show_list(my_list, "\n(list still empty) ");
while (0 == insert_list(my_pair, my_list)) {};
show_list(
my_list, "\n(list now filled with same pair ");
my_pair = delete_pair(my_pair); // free test_pair
my_list = delete_list(my_list); // free list
return 0;
}
outout 对于此示例
test_pair is [ ("Stack",42), ("Overflow",-42)]
(list still empty) [0/5] element(s) in list
(list now filled with same pair [5/5] element(s) in list
[ ("Stack",42), ("Overflow",-42)]
[ ("Stack",42), ("Overflow",-42)]
[ ("Stack",42), ("Overflow",-42)]
[ ("Stack",42), ("Overflow",-42)]
[ ("Stack",42), ("Overflow",-42)]
deleting 5 elements list
一个简单的实现,用于非平凡的Element
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
char* name;
int code;
} Element;
Element* copy_el(Element*);
Element* delete_el(Element*);
int show_el(Element*, const char*);
Element* copy_el(Element* orig)
{
if (orig == NULL) return NULL;
Element* one = malloc(sizeof(Element));
if (one == NULL) return NULL;
one->name = malloc(1 + strlen(orig->name));
if (one->name == NULL)
{
free(one);
return NULL;
}
strcpy(one->name, orig->name);
one->code = orig->code;
return one;
}
Element* delete_el(Element* orig)
{
if (orig == NULL) return NULL;
free(orig->name);
free(orig);
return NULL;
}
int show_el(Element* el, const char* msg)
{
if (el == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf(" (\"%s\",%d)", el->name, el->code);
return 0;
}
Element* make_el(const char* name, const int code)
{
if (name == NULL) return NULL;
Element* one = malloc(sizeof(Element));
if (one == NULL) return NULL;
one->name = malloc(1 + strlen(name));
if (one->name == NULL)
{
free(one);
return NULL;
}
strcpy(one->name, name);
one->code = code;
return one;
}```
### A note on *encapsulation* ###
Here in `insert_list` a pair is inserted
```C
Pair* pair = make_pair(p->first, p->second);
但做到这一点的代码来自实现。Pair
但是在make_pair
one->first = copy_el(a);
one->second = copy_el(b);
我们看到使用了用户提供的副本创建器。
关于构建此内容的说明
- 每个级别的容器通常都会添加一对文件、一个头文件和实现文件。
.h
.c
- 每个级别只能看到下面的级别,并且代码只是复制和改编
- 通常有一个构造函数、一个析构函数、一个复制构造函数、一个打印函数,声明为 here 或遵循某种模式。它通常工作正常,因为这是在其他领域和语言中所做的。
代码和 Visual Studio 项目可在此链接的 GitHub 上找到
评论
*_el()
Element* third;
pair
pairs
element1
element2
first
second
first
second
third
Pair
Pair
insert
first
second
C++
copy_el()
make_el()
评论
my_list.pairs[0].element1 = (struct element*)malloc(sizeof(struct element));
后跟 将覆盖指针。这与 You 替换变量的值并丢失原始值的情况类似。my_list.pairs[0].element1 = &element1;
int a; a = 10; a = 5;
malloc