program story

순수한 C로 RAII를 구현합니까?

inputbox 2020. 12. 12. 10:41
반응형

순수한 C로 RAII를 구현합니까?


순수한 C로 RAII 를 구현할 수 있습니까?

나는 그것이 정상적인 방법으로 가능하지 않다고 생각하지만 아마도 일종의 더러운 속임수를 사용하는 것이 가능할 것입니다. 표준 free함수의 오버로드 가 떠오르거나 스택의 반환 주소를 덮어 써서 함수가 반환 될 때 어떻게 든 리소스를 해제하는 다른 함수를 호출합니까? 아니면 setjmp / longjmp 트릭으로?

이것은 순전히 학문적 관심사이며 실제로 그렇게 이식 할 수없고 미친 코드를 작성할 의도는 없지만 그것이 가능한지 궁금합니다.


표준에는 그러한 가능성이 포함되어 있지 않기 때문에 이것은 고유 한 구현에 따라 다릅니다. 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

여기를 참조 하십시오


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와 같은 고급 매크로 프레임 워크를 사용하고 싶을 것입니다.


컴파일러가 C99 (또는 그것의 상당 부분)를 지원하는 경우 다음과 같은 가변 길이 배열 (VLA)을 사용할 수 있습니다.

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

    // ...
}

메모리가 제공되는 경우 gcc는 C99에 추가되기 훨씬 전에이 기능을 보유 / 지원했습니다. 이것은 (대략) 다음과 같은 간단한 경우와 같습니다.

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

그러나 파일 닫기, 데이터베이스 연결 등과 같이 dtor가 수행 할 수있는 다른 작업을 수행 할 수는 없습니다.


아마도 가장 쉬운 방법은 goto를 사용하여 함수의 끝 부분에있는 레이블로 이동하는 것입니다. 그러나 그것은 아마도 여러분이보고있는 종류에 비해 너무 수동적 일 것입니다.


스택의 반환 주소를 덮어 쓰도록 선택했습니다. 가장 투명하게 작동합니다. 바꾸기 free는 힙 할당 "개체"에서만 작동합니다.


alloca ()를 보셨습니까? var가 범위를 벗어날 때 해제됩니다. 하지만 효과적으로 사용하려면 호출자는 항상 alloca를 수행해야합니다. strdup을 구현하고 있다면 alloca를 사용할 수 없습니다.


이봐, 당신은 CFront 를 다시 만들 려고 해요!


https://github.com/psevon/exceptions-and-raii-in-c 에서 고유하고 공유 된 스마트 포인터 및 예외의 C 구현을 확인 하십시오 . 이 구현은 매크로 대괄호 BEGIN ... END에 의존하여 중괄호를 대체하고 범위를 벗어나는 스마트 포인터를 감지하며 리턴을위한 매크로 대체를 사용합니다.


전에는 속성 정리에 대해 몰랐습니다. 확실히 적용 가능한 깔끔한 솔루션이지만 setjmp / longjmp 기반 예외 구현에서는 제대로 작동하지 않는 것 같습니다. 정리 함수는 예외를 발생시킨 범위와이를 포착하는 범위 사이의 중간 범위 / 함수에 대해 호출되지 않습니다. Alloca에는이 문제가 없지만, alloca를 사용하면 메모리가 스택 프레임에서 할당되기 때문에 호출 한 함수에서 메모리 청크의 소유권을 외부 범위로 전송할 수 없습니다. C ++ unique_ptr 및 shared_ptr과 다소 유사한 스마트 포인터를 구현할 수 있습니다. {} 대신 매크로 대괄호를 사용해야하고 추가 논리를 범위 진입 / 종료에 연결할 수 있도록 반환해야한다고 생각했습니다. https://github.com/psevon/exceptions-and-raii-in-c 에서 autocleanup.c를 참조하십시오. 구현을 위해.


Johannes의 답변에서이 부분을 보완하려면 :

정리 속성은 변수가 범위를 벗어날 때 함수를 실행합니다.

정리 속성 ( 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

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);
}

참고 URL : https://stackoverflow.com/questions/368385/implementing-raii-in-pure-c

반응형