Автоматический выпуск переменных стека в C
К сожалению, в C нет никаких интеллектуальных указателей.. но возможно ли построить макрос, который объявляет переменную переменной и вызывать вызов функции с этой переменной в качестве входной переменной после выхода из области, в которой была объявлена переменная?
Извините за длинную фразу, но я работаю над ядром xnu, где у вас много элементов, у которых есть встроенные счетчики ссылок, и не нужно забывать об исправлении этого элемента, когда это делается с его помощью, чтобы избежать утечек памяти.
Например, если у меня есть следующий тип proc_t
:
struct proc;
typedef struct proc * proc_t;
Я хочу объявить переменную стека на основе этого типа в пределах области видимости, например:
{
proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid);
//the rest of the code in this scope
}
После препроцессора проанализируйте макрос и перед компиляцией, следующий код, который я ожидаю получить:
{
proc_t myproc = proc_find(mypid)
//the rest of the code in scope
proc_rele(myproc);
}
Есть ли способ определить такой макрос, как в C?
Ответы
Ответ 1
В GCC можно использовать атрибут переменной очистка. Пожалуйста, взгляните на это:
http://echorand.me/site/notes/articles/c_cleanup/cleanup_attribute_c.html
Пример кода:
#include <stdio.h>
#include <stdlib.h>
void free_memory(void **ptr)
{
printf("Free memory: %p\n", *ptr);
free(*ptr);
}
int main(void)
{
// Define variable and allocate 1 byte, the memory will be free at
// the end of the scope by the free_memory function. The free_memory
// function will get the pointer to the variable *ptr (double pointer
// **ptr).
void *ptr __attribute__ ((__cleanup__(free_memory))) = malloc(1);
return 0;
}
Если вы сохраните исходный код в файле с именем main.c, вы можете скомпилировать его с помощью этой команды:
gcc main.c -o main
и проверьте, нет ли утечек памяти:
valgrind ./main
Пример вывода valgrind:
==1026== Memcheck, a memory error detector
==1026== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==1026== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==1026== Command: ./main
==1026==
Free memory: 0x51ff040
==1026==
==1026== HEAP SUMMARY:
==1026== in use at exit: 0 bytes in 0 blocks
==1026== total heap usage: 1 allocs, 1 frees, 1 bytes allocated
==1026==
==1026== All heap blocks were freed -- no leaks are possible
==1026==
==1026== For counts of detected and suppressed errors, rerun with: -v
==1026== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Ответ 2
C действительно обеспечивает способ размещения кода синтаксически перед другим кодом, который будет выполняться первым: блок for
. Помните, что раздел 3 структуры for
может содержать произвольное выражение и всегда запускается после выполнения основного блока.
Таким образом, вы можете создать макрос, который делает предопределенный вызов после заданного фрагмента следующего кода, обертывая блок for
в макросе:
#define M_GEN_DONE_FLAG() _done_ ## __LINE__
#define M_AROUND_BLOCK2(FLAG, DECL, BEFORE, AFTER) \
for (int FLAG = (BEFORE, 0); !FLAG; ) \
for (DECL; !FLAG; FLAG = (AFTER, 1))
#define M_AROUND_BLOCK(DECL, BEFORE, AFTER) M_AROUND_BLOCK2(M_GEN_DONE_FLAG(), DECL, BEFORE, AFTER)
#define M_CLEANUP_VAR(DECL, CLEANUP_CALL) M_AROUND_BLOCK(DECL, (void)0, CLEANUP_CALL)
... и вы можете использовать его следующим образом:
#include <stdio.h>
struct proc;
typedef struct proc * proc_t;
proc_t proc_find(int);
void proc_rele(proc_t);
void fun(int mypid) {
M_CLEANUP_VAR (proc_t myproc = proc_find(mypid), proc_rele(myproc))
{
printf("%p\n", &myproc); // just to prove it in scope
}
}
Трюк здесь в том, что блок for
принимает следующий оператор, но если мы фактически не ставим этот оператор в определение макроса, мы можем следить за вызовом макроса с нормальным блоком кода, и он будет "волшебным", относятся к нашему новому синтаксису структуры управления видимостью, просто благодаря расширенному for
.
Любой оптимизатор, который стоит использовать, удалит флаг цикла с минимальными настройками оптимизации. Обратите внимание, что имя, столкнувшееся с флагом, не вызывает большого беспокойства (т.е. Для этого вам действительно не нужен gensym
), потому что флаг привязан к телу цикла, и любые вложенные циклы будут безопасно скрывать его, если они используют тот же самый имя флага.
Бонус здесь заключается в том, что область действия для очистки ограничена (она не может использоваться вне соединения сразу после ее объявления) и визуально явным (из-за указанного соединения).
Плюсы:
- это стандарт C без расширений
- поток управления прост.
- на самом деле (так или иначе) менее подробный, чем
__attribute__ __cleanup__
Минусы:
- он не предоставляет "полный" RAII (т.е. не будет защищать от исключений
goto
или С++: __cleanup__
обычно реализуется с машинами С++ под капотом, чтобы он был более полным). Более серьезно, он не защищает от раннего return
(спасибо @Voo). (Вы можете по крайней мере защитить от неулокального break
- если хотите - добавив третью строку, switch (0) default:
в конец M_AROUND_BLOCK2
.)
- не все согласны с расширяющими синтаксис макросами (но учтите, что вы расширяете семантику C здесь, поэтому...)
Ответ 3
Я знаю, что это не то, что вы хотите услышать, но я призываю вас не делать этого.
Совершенно допустимый стиль C имеет единственную точку возврата, перед которой все очищается. Поскольку исключений нет, это легко сделать и легко проверить, посмотрев на функцию.
Использование макро-хакеров или компиляторов "функции" для этого не принимаются в стиле C. Это станет бременем для всех, кто вас прочитает и поймет. И, в конце концов, это на самом деле не сильно вас выгоняет.