Довольно занятная ситуация: чем больше я пишу кода, тем более простой код мне нравится писать. Что интересно, наиболее простой код выходит не на C++ или Objective-C и даже не на Python, а на чистом C. Хотя, оно и понятно, нет тут простора для творчества
А вот в самом C есть один основательно напрягающий меня момент: освобождение памяти. Довольно часто приходится работать со структурами, в состав которых входят массивы, строки переменной длинны или массивы структур со всей этой радостью одновременно. А раз уж от создания такой конструкции никуда не денешься, то как минимум хочется иметь возможность ее легко удалить.
Подход с использование garbage collector мне не очень нравится, все же это как из пушки по воробьям… А вот построение из выделяемой памяти подобия дерева, мне показалось куда более интересным.
Например, имеется массив структур:
{
char *string;
char **string_array;
};
typedef struct test_struct test_struct_t;
Который инциализируется и освобождается стандартными методами:
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 );
И если инициализация никаких нареканий не вызывает, то вот освобождение данных меня напрягает. Куда удобнее, если при удалении массива удаляются и все содержащиеся в нем структуры. В то же время, необходимо иметь возможность удалить только одну структуру, не затрагивая остальные. Решение выглядит так:
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 падения производительности вообще не должно быть заметно.