Управление памятью в ANSI C

Довольно занятная ситуация: чем больше я пишу кода, тем более простой код мне нравится писать. Что интересно, наиболее простой код выходит не на C++ или Objective-C и даже не на Python, а на чистом C. Хотя, оно и понятно, нет тут простора для творчества
А вот в самом C есть один основательно напрягающий меня момент: освобождение памяти. Довольно часто приходится работать со структурами, в состав которых входят массивы, строки переменной длинны или массивы структур со всей этой радостью одновременно. А раз уж от создания такой конструкции никуда не денешься, то как минимум хочется иметь возможность ее легко удалить.
Подход с использование garbage collector мне не очень нравится, все же это как из пушки по воробьям… А вот построение из выделяемой памяти подобия дерева, мне показалось куда более интересным.
Например, имеется массив структур:

struct test_struct
{
    char *string;
    char **string_array;
};
typedef struct test_struct test_struct_t;

Который инциализируется и освобождается стандартными методами:

test_struct_t *item, **array = malloc( sizeof(test_struct_t*) * structs_count );
int i, j;
for( i = 0; i < structs_count; ++i )
{
    item = malloc( sizeof( test_struct_t ) );
    item->string = malloc( string_len );
    item->string_array = malloc( sizeof( char* ) * string_array_len );
    for( j = 0; j < string_array_len; ++j )
        item->string_array[j] = malloc( string_len );
    array[i] = item;
}

for( i=0; i < structs_count; ++i )
{
    item = array[i];
    free( item->string );
    for( j = 0; j < string_array_len; ++j )
        free( item->string_array[j] );
    free( item->string_array );
    free( item );
}
free( array );

И если инициализация никаких нареканий не вызывает, то вот освобождение данных меня напрягает. Куда удобнее, если при удалении массива удаляются и все содержащиеся в нем структуры. В то же время, необходимо иметь возможность удалить только одну структуру, не затрагивая остальные. Решение выглядит так:

test_struct_t *item, **array = mt_malloc( sizeof(test_struct_t*) * structs_count, (void*)0 );
int i, j;
for( i = 0; i < structs_count; ++i )
{
    item = mt_malloc( sizeof( test_struct_t ), array );
    item->string = mt_malloc( string_len, item );
    item->string_array = mt_malloc( sizeof( char* ) * string_array_len, item );
    for( j = 0; j < string_array_len; ++j )
        item->string_array[j] = mt_malloc( string_len, item->string_array );
    array[i] = item;
}

mt_free( array );

Таким образом, для каждого из элементов устанавливается родитель, при удалении которого, по цепочке удаляются все дочерние элементы. Первоначально, для хранения информации о зависимостях, я решил использовать дерево, исходя из того, что в случае удаления не всего дерева зависимостей целиком, а лишь одного из узлов, это даст большую скорость. Но в то же время, куда более распространенная операция не удаления одного из узлов, а добавление элементов или удаление дерева целиком. По результатам тестовых прогонов выходит, что на операциях добавления разница в скорости между деревом и списком ~1.5-2 раза. Вобщем, я остановился на списке.
То что вышло в итоге, можно найти на GitHub под кодовым именем memtree. На данный момент имеется одна довольно серьезная проблема: производительность сильно просаживается на больших объемах данных. Но, во-первых, я над этим вопросом работаю, а во-вторых, на небольших размерах падения производительности практически не заметно и оно вполне приемлимо. Так, например, на моем Tasks Explorer падения производительности вообще не должно быть заметно.

Leave a Reply