Реализация RAII в чистом C? - PullRequest
       29

Реализация RAII в чистом C?

55 голосов
/ 15 декабря 2008

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

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

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

Ответы [ 11 ]

70 голосов
/ 15 декабря 2008

Это зависит от реализации, так как Стандарт не включает такую ​​возможность. Для 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

См. здесь

10 голосов
/ 08 июня 2013

Одним из решений для перевода 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, чтобы сделать что-то похожее на описанное выше.

9 голосов
/ 08 июля 2010

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

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

    // ...
}

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

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

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

3 голосов
/ 15 декабря 2008

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

1 голос
/ 24 апреля 2009

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

1 голос
/ 15 декабря 2008

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

0 голосов
/ 28 апреля 2019
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);
}
0 голосов
/ 25 апреля 2014

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

атрибут 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
0 голосов
/ 08 апреля 2014

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

0 голосов
/ 07 апреля 2014

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

...