第一篇文章,长期读者。我正在学习C,但是来自多年的Python经验。很抱歉,如果这是重新发布,但我甚至不知道在搜索Google时使用的正确词汇表。
我认为我仍然陷入一种面向对象的心态,这导致我在C中愚蠢的事情。我有一个类型“my_type”,其中包含指向第二种类型“my_array”的指针。以我的OO思维方式,我想为my_type编写New和Free函数。在初始化期间,my_type应该创建一个新的my_array并存储它的指针。在销毁过程中,my_type的析构函数应该调用my_array的析构函数并释放动态分配的内存。
此代码编译但会产生令人讨厌的错误
#include <stdio.h>
#include <stdlib.h>
/**
* @brief A type implementing a dynamically allocated array
*/
typedef struct my_array {
int n;
double *array;
} my_array;
/**
* @brief A type containing a my_array pointer
*/
typedef struct my_type {
my_array *a;
} my_type;
/**
* @brief Initialize the dynamically allocated array
*
* @param ma pointer to a my_array
* @param n number of array elements
*/
void my_array_new(my_array *ma, int n) {
ma->n = n;
ma->array = (double *) malloc(n * sizeof(double));
}
/**
* @brief Free dynamically allocated memory of a my_array
*
* @param ma pointer to a my_array
*/
void my_array_free(my_array *ma) {
free(ma->array);
}
/**
* @brief Initialize a my_type
*
* @param mt pointer to a my_type
* @param n number of array elements
*/
void my_type_new(my_type *mt, int n) {
/* Create a local my_array */
my_array a;
my_array_new(&a, n);
/* Store pointer to a */
mt->a = &a;
/* I am holding on to the pointer for a, but does a fall out of scope
* here? Is this the origin of the error? */
}
/**
* @brief Free allocated my_type
*
* @param mt pointer to my_type
*/
void my_type_free(my_type *mt) {
my_array_free(mt->a);
}
int main(int argc, char *argv[]) {
my_type mt;
int n = 10;
printf("Initializing my_type %p\n", &mt);
my_type_new(&mt, n);
printf("mt.a->n = %d\n", mt.a->n);
printf("Freeing my_type %p\n", &mt);
my_type_free(&mt);
return 0;
}
这是错误
Initializing my_type 0x7ffede7434c0
mt.a->n = 10
Freeing my_type 0x7ffede7434c0
*** Error in `./test': double free or corruption (out): 0x00007ffede7434c0 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3e5f077d9e]
/lib64/libc.so.6(cfree+0x5b5)[0x3e5f0839f5]
./test[0x400618]
./test[0x400662]
./test[0x4006da]
/lib64/libc.so.6(__libc_start_main+0xf0)[0x3e5f01ffe0]
./test[0x4004f9]
我想我知道这里发生了什么。当我致电my_type_new
时,我会创建一个本地my_array
变量。当函数结束时,这超出了范围,但我仍然存储了指针。现在它变得模糊:正式分配给标记为垃圾的mt.a
的内存,但是我仍然可以通过解除引用指针来读出它的值吗?
my_type_new
创建和存储my_array
的正确方法是什么?
我对所有建议持开放态度,但由于其他原因,我不能在这里使用C ++类。
答案 0 :(得分:1)
这里的问题是my_type
指向在堆栈上分配但不再存在的对象。因此,当您尝试释放它时,您会收到错误,因为free
函数知道您尝试释放的内存块不会在可以解除分配的堆上阻塞。
如果您想以这种方式执行操作,初始化my_type
实例的函数应为my_array
分配空间。除了数组的空间之外,析构函数应该释放这个空间。具体做法是:
void my_type_new(my_type *mt, int n) {
/* Create a local my_array */
mt->a = malloc(sizeof *mt->a);
my_array_new(mt->a, n);
}
void my_type_free(my_type *mt) {
my_array_free(mt->a);
free(mt->a);
}
理想情况下,您还可以在释放内存块后检查是否有错误分配了足够的空间和NULL
指针。
我可能还会提到,您可能会发现最好将这些类型的对象存储到位。换句话说,如果my_type
总是需要my_array
的单个实例,并且您不希望它永远为空,那么您可以直接在my_array
实例中包含my_type
实例结构定义typedef struct my_type {
my_array a;
my_type;
然后初始化它。这样做的好处是可以减少所定义对象所需的分配和释放。所以,例如,
my_type
如果你采用这种方法,那么void my_type_new(my_type *mt, int n) {
my_array_new(&mt->a, n);
}
void my_type_free(my_type *mt) {
my_array_free(&mt->a);
}
的初始化器和析构函数看起来像这样:
my_array
请注意,您不再需要为my_type
分配额外空间,因为它直接作为//Take the strings "new" and "variable" and make them into a variable.
"new" + "variable" = "Hello World";
//So technically the variable name is "newvariable" and has the value "Hello World"
的成员包含。
作为一个注释,这种面向对象的方法是完全有效的。我更喜欢它,部分原因是因为它对那些不熟悉C的人非常熟悉。但也因为我觉得它对组织数据和功能很有效。
您可能对this read感兴趣。
答案 1 :(得分:0)
问题在于此功能
void my_type_new(my_type *mt, int n) {
/* Create a local my_array */
my_array a;
my_array_new(&a, n);
/* Store pointer to a */
mt->a = &a;
/* I am holding on to the pointer for a, but does a fall out of scope
* here? Is this the origin of the error? */
}
my_array a;
变量是函数的本地变量,因此当函数返回时它会自动释放,之后您无法使用它,因为行为未定义,您正在存储它& #39; a
mt
的{{1}}个地址。然后你free()
它,这是另一个错误。
它仍然可读,因为它不一定在free()
之后立即被覆盖,如果从操作系统请求更多内存,它就可用。
你也应该malloc()
,就像这样
void my_type_new(my_type *mt, int n) {
/* Create a local my_array */
my_array *a;
a = malloc(sizeof(*a));
if (a != NULL)
my_array_new(a, n);
/* Store pointer to a */
mt->a = a;
/* I am holding on to the pointer for a, but does a fall out of scope
* here? Is this the origin of the error? */
}
因为执行此操作可能会使mt->a
保留NULL
值,所以在取消引用之前必须先对NULL
进行检查。
答案 2 :(得分:0)
/* I am holding on to the pointer for a, but does a fall out of scope
* here? Is this the origin of the error? */
是的,a
超出范围,导致您失去对malloc
编辑的内存的引用。这会导致内存泄漏;你的程序消耗的动态内存不能free
。更糟糕的是,因为您已经保留了对超出范围的变量的引用,您稍后尝试访问该变量会导致未定义的行为(您看到的错误)。
幸运的是,对于您的情况,解决方案很简单。如果您将void my_type_new(my_type *mt, int n)
更改为my_array my_type_new(my_type *mt, int n)
,则可以return a;
,只要您将my_type_new
的返回值分配到my_array
,您仍然可以访问free
记忆以便my_array my_type_new(my_type *mt, int n) {
my_array a;
my_array_new(&a, n);
return a;
}
。
例如:
my_array foo = my_type_new(...);
......使用它的简略示例:
my_type_free(&foo);
...并使用析构函数:
tif
答案 3 :(得分:0)
我写这个作为答案,因为评论太长了,你一般都会提出建议。
您使用的方法很快就会成为维护的噩梦,而不是谈论修改或重构。
使用指针链也是一个性能限制器,因为必须明确获取所有其他对象。
如果您使用gcc并且允许使用其C语言扩展,请查看plan9-extensions。除了标准(自C99)匿名结构字段之外,这还允许更清晰的OOP方法。
即使使用标准C99,你也可以通过后一种功能和类型转换更好地相处。