2015-06-26 4 views
-1

Первый пост, долго читатель. Я изучаю C, но из многолетнего опыта работы с Python. Извините, если это repost, но я даже не знаю, какой правильный словарь использовать при поиске в Google.Сохранение указателей на локальные переменные области

Я думаю, что я все еще застрял в объектно-ориентированном мышлении, что приводит меня к глупости в C. У меня есть тип «my_type», который содержит указатель на второй тип «my_array». В моем мышлении OO я хочу писать новые и бесплатные функции для my_type. Во время инициализации 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++ здесь по другим причинам.

+2

Стандартное предупреждение: не накладывайте 'void *' как возвращаемые 'malloc' и друзьями указатели. – Olaf

+2

[Я делаю результат 'malloc'?] (Http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) – Sebivor

ответ

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; является локальным для функции, так как он автоматически освобождается, когда функция возвращает, и вы не можете использовать его после этого, потому что тогда поведение не определено, вы храните его адрес в a poitner mt. Затем вы получите 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, прежде чем разыменования его.

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.

В качестве примечания, этот объектно-ориентированный подход совершенно применим. Я предпочитаю это, отчасти потому, что он так хорошо знаком с людьми, которые плохо знакомы с C. Но также потому, что я считаю это эффективным для организации данных и функциональности.

Возможно, вас заинтересует this read.

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); 
0

Я пишу это как ответ, поскольку он слишком длинный для комментария, и вы попросили предложения в целом.

Подход, который вы используете, скоро станет кошмаром для поддержания, а не говорить об изменении или рефакторе.

Вы приближаетесь, используя цепочку указателей, также является ограничителем производительности, так как все остальные объекты должны быть выбраны явно.

Если вы используете gcc и разрешено использовать его расширения для языка C, посмотрите на plan9-extensions. Помимо стандартного (начиная с C99) анонимных структурных полей, это позволяет использовать более чистый подход ООП.

Даже со стандартным C99, вы можете лучше ладить с последними функциями и типами отливок.