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

Внедрение RAII в чистом C?

Возможно ли реализовать RAII в чистом C?

Я предполагаю, что это невозможно в здравом смысле, но, возможно, возможно, используя какой-то грязный трюк. Перегрузка стандартной функции free приходит в голову или, возможно, переписывает обратный адрес в стеке, поэтому, когда функция возвращается, она вызывает некоторую другую функцию, которая каким-то образом освобождает ресурсы? Или, может быть, с трюком setjmp/longjmp?

Это чисто академический интерес, и я не собираюсь писать такой непортативный и сумасшедший код, но мне интересно, возможно ли это вообще.

4b9b3361

Ответ 1

Это зависит от реализации, поскольку стандарт не включает такую ​​возможность. Для GCC атрибут cleanup выполняет функцию, когда переменная выходит за пределы области видимости:

#include <stdio.h>

void scoped(int * pvariable) {
    printf("variable (%d) goes out of scope\n", *pvariable);
}

int main(void) {
    printf("before scope\n");
    {
        int watched __attribute__((cleanup (scoped)));
        watched = 42;
    }
    printf("after scope\n");
}

Печать

before scope
variable (42) goes out of scope
after scope

Смотрите здесь

Ответ 2

Одним из решений для перевода RAII в C (когда у вас нет cleanup()) - заключить вызов функции в код, который будет выполнять очистку. Это также может быть упаковано в аккуратный макрос (показано в конце).

/* Publicly known method */
void SomeFunction() {
  /* Create raii object, which holds records of object pointers and a
     destruction method for that object (or null if not needed). */
  Raii raii;
  RaiiCreate(&raii);

  /* Call function implementation */
  SomeFunctionImpl(&raii);

  /* This method calls the destruction code for each object. */
  RaiiDestroyAll(&raii);
}

/* Hidden method that carries out implementation. */
void SomeFunctionImpl(Raii *raii) {
  MyStruct *object;
  MyStruct *eventually_destroyed_object;
  int *pretend_value;

  /* Create a MyStruct object, passing the destruction method for
     MyStruct objects. */
  object = RaiiAdd(raii, MyStructCreate(), MyStructDestroy);

  /* Create a MyStruct object (adding it to raii), which will later
     be removed before returning. */
  eventually_destroyed_object = RaiiAdd(raii,
      MyStructCreate(), MyStructDestroy);

  /* Create an int, passing a null destruction method. */
  pretend_value = RaiiAdd(raii, malloc(sizeof(int)), 0);

  /* ... implementation ... */

  /* Destroy object (calling destruction method). */
  RaiiDestroy(raii, eventually_destroyed_object);

  /* or ... */
  RaiiForgetAbout(raii, eventually_destroyed_object);
}

Вы можете выразить весь код панели котла в SomeFunction с помощью макросов, поскольку он будет одинаковым для каждого вызова.

Например:

/* Declares Matrix * MatrixMultiply(Matrix * first, Matrix * second, Network * network) */
RTN_RAII(Matrix *, MatrixMultiply, Matrix *, first, Matrix *, second, Network *, network, {
  Processor *processor = RaiiAdd(raii, ProcessorCreate(), ProcessorDestroy);
  Matrix *result = MatrixCreate();
  processor->multiply(result, first, second);
  return processor;
});

void SomeOtherCode(...) {
  /* ... */
  Matrix * result = MatrixMultiply(first, second, network);
  /* ... */
}

Примечание: вы хотели бы использовать расширенную структуру макросов, такую как P99, чтобы сделать что-то подобное вышеописанным возможным.

Ответ 3

Если ваш компилятор поддерживает C99 (или даже значительную его часть), вы можете использовать массив переменной длины (VLA), например:

int f(int x) { 
    int vla[x];

    // ...
}

Если память используется, gcc имеет/поддерживает эту функцию задолго до ее добавления в C99. Это (примерно) эквивалентно простому случаю:

int f(int x) { 
    int *vla=malloc(sizeof(int) *x);
    /* ... */
    free vla;
}

Однако он не позволяет делать какие-либо другие действия, которые может выполнять dtor, например, закрывать файлы, подключения к базам данных и т.д.

Ответ 4

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

Ответ 5

Я бы выбрал перезапись обратного адреса в стеке. Это было бы самым прозрачным. Замена free будет работать только с выделенными кучей "объектами".

Ответ 6

Вы посмотрели на alloca()? Он освободится, когда var покинет область действия. Но чтобы использовать его эффектно, вызывающий должен всегда делать alloca перед отправкой его на вещи... Если вы выполняли strdup, ну, вы не можете использовать alloca.

Ответ 7

Эй, вы пытаетесь воссоздать CFront!

Ответ 8

Отметьте https://github.com/psevon/exceptions-and-raii-in-c для реализации C уникальных и общих интеллектуальных указателей и исключений. Эта реализация основывается на макрокомандах BEGIN... END, заменяя фигурные скобки и обнаруживая, что интеллектуальные контроллеры выходят за пределы области видимости, а также макросмена для возврата.

Ответ 9

Я раньше не знал об очистке атрибутов. Разумеется, это четкое решение, в котором оно применимо, но, похоже, оно плохо себя ведет с реализациями исключений на основе setjmp/longjmp; функция очистки не вызывается для каких-либо промежуточных областей/функций между областью действия, которая бросает исключение и область, которая его ловит. У Alloca нет этой проблемы, но с помощью alloca вы не можете передать право собственности на кусок памяти на внешнюю область из функции, которая вызвала ее, поскольку память выделена из фрейма стека. Возможно реализовать интеллектуальные контроллеры, несколько схожие с С++ unique_ptr и shared_ptr, считая, что для этого требуется использовать макро скобки вместо {} и вернуться, чтобы иметь возможность связывать дополнительную логику с входом/выходом области. См. Autocleanup.c в https://github.com/psevon/exceptions-and-raii-in-c для реализации.

Ответ 10

В дополнение к этой части ответа Йоханнеса:

атрибут cleanup запускает функцию, когда переменная выходит за пределы области

Существует ограничение на атрибут очистки (http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Variable-Attributes.html): Этот атрибут может применяться только к переменным области видимости автоматической функции.

Итак, если в файле есть статическая переменная, можно реализовать RAII для статической переменной следующим образом:

#include <stdio.h>
#include <stdlib.h>

static char* watched2;

__attribute__((constructor))
static void init_static_vars()
{
  printf("variable (%p) is initialazed, initial value (%p)\n", &watched2, watched2);
  watched2=malloc(1024);
}


__attribute__((destructor))
static void destroy_static_vars()
{
  printf("variable (%p), value( %p) goes out of scope\n", &watched2, watched2);
  free(watched2);
}

int main(void)
{
  printf("exit from main, variable (%p) value(%p) is static\n", &watched2, watched2);
  return 0;
}

Это тест:

>./example
variable (0x600aa0) is initialazed, initial value ((nil))
exit from main, variable (0x600aa0) value(0x16df010) is static
variable (0x600aa0), value( 0x16df010) goes out of scope

Ответ 11

я реализую aС# используя стиль raii для языка c в чистом c и некоторые строки asm, скоро опубликую src на github

Ответ 12

my implementation of raii for c in pure c and minimal asm
@ https://github.com/smartmaster/sml_clang_raii

**RAII for C language in pure C and ASM**

**featurs : **

-easy and graceful to use
- no need seperate free cleanup functions
- able to cleanup any resources or call any function on scope exits


**User guide : **

-add source files in src folder to your project
-include sml_raii_clang.h in.c file
-annote resource and its cleanup functions

/* образец кода */

void sml_raii_clang_test()
{
    //start a scope, the scope name can be any string
    SML_RAII_BLOCK_START(0);


    SML_RAII_VOLATILE(WCHAR*) resA000 = calloc(128, sizeof(WCHAR)); //allocate memory resource
    SML_RAII_START(0, resA000); //indicate starting a cleanup code fragment, here 'resA000' can be any string you want
    if (resA000) //cleanup code fragment
    {
        free(resA000);
        resA000 = NULL;
    }
    SML_RAII_END(0, resA000); //indicate end of a cleanup code fragment


    //another resource
    //////////////////////////////////////////////////////////////////////////
    SML_RAII_VOLATILE(WCHAR*) res8000 = calloc(128, sizeof(WCHAR));
    SML_RAII_START(0, D000);
    if (res8000)
    {
        free(res8000);
        res8000 = NULL;
    }
    SML_RAII_END(0, D000);


    //scope ended, will call all annoated cleanups
    SML_RAII_BLOCK_END(0);
    SML_RAII_LABEL(0, resA000); //if code is optimized, we have to put labels after SML_RAII_BLOCK_END
    SML_RAII_LABEL(0, D000);
}