保持指向局部范围变量的指针

时间:2015-06-26 01:03:44

标签: c pointers

第一篇文章,长期读者。我正在学习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 ++类。

4 个答案:

答案 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,你也可以通过后一种功能和类型转换更好地相处。