Подтвердить что ты не робот

Автоматический выпуск переменных стека в 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?

4b9b3361

Ответ 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. Это станет бременем для всех, кто вас прочитает и поймет. И, в конце концов, это на самом деле не сильно вас выгоняет.