Automatyczne uwalnianie zmiennych stosu w C

Niestety, w C nie ma żadnych inteligentnych wskaźników.. ale czy jest możliwe zbudowanie makra, które zawija deklarację zmiennej i wywołuje wywołanie funkcji z tą zmienną jako zmienną wejściową po opuszczeniu zakresu, w którym zmienna została zadeklarowana ?

Przepraszam za długie zdanie, ale pracuję nad jądrem xnu, gdzie masz wiele elementów, które mają wbudowane liczniki referencyjne, i nie wolno zapomnieć o odrefowaniu tego elementu, gdy go używasz, aby uniknąć wycieków pamięci.

Dla przykład, jeśli mam następujący typ proc_t:

struct proc;
typedef struct proc * proc_t;

Chcę zadeklarować zmienną stosu na podstawie tego typu w ramach zakresu, na przykład:

{
    proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid);
    //the rest of the code in this scope 
}

Po przeanalizowaniu makra przez preprocesor i przed kompilacją, oczekuję wygenerowania następującego kodu:

{ 
    proc_t myproc = proc_find(mypid)
    //the rest of the code in scope
    proc_rele(myproc);
}

Czy Jest jakiś sposób na zdefiniowanie takiego makra jak w C ?

Author: rsp, 2017-06-29

3 answers

Możesz użyć atrybutu zmiennej cleanup w GCC. Proszę spojrzeć na to: http://echorand.me/site/notes/articles/c_cleanup/cleanup_attribute_c.html

Przykładowy kod:

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

Jeśli zapiszesz kod źródłowy w pliku o nazwie main.c, możesz skompilować to poleceniem:

gcc main.c -o main

I sprawdzić, czy są jakieś wycieki pamięci przez:

valgrind ./main

Przykładowe wyjście z 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)
 39
Author: Maciej Malinowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-21 11:25:25

C zapewnia sposób na składniowe umieszczenie kodu przed innym kodem, który zostanie uruchomiony jako pierwszy: blok for. Pamiętaj, że klauzula 3 struktury for może zawierać dowolne wyrażenie i zawsze jest uruchamiana po wykonaniu głównego bloku.

Można zatem utworzyć makro, które wykonuje z góry określone wywołanie po danym fragmencie po kodzie, owijając w makro blok 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)

...i możesz go używać w ten sposób:

#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's in scope
  }
}

The sztuczka polega na tym, że blok for akceptuje następujące polecenie, ale jeśli nie umieścimy tego polecenia w definicji makra, możemy podążać za wywołaniem makra normalnym blokiem kodu i będzie on "magicznie" należał do naszej nowej składni scoped-control-structure, po prostu przez zastosowanie rozszerzonego for.

Każdy optymalizator, którego warto użyć, usunie flagę pętli przy najniższych ustawieniach optymalizacji. Zauważ, że nazwa zderzająca się z flagą nie jest wielkim problemem (tzn. nie do tego potrzebny jest gensym), ponieważ flaga jest ustawiona na ciele pętli, a każda zagnieżdżona pętla bezpiecznie ją ukryje, jeśli użyje tej samej nazwy flagi.

Bonus polega na tym, że zakres zmiennej do czyszczenia jest ograniczony (nie może być używany poza związkiem bezpośrednio po jego deklaracji) i wizualnie wyraźny (z powodu wspomnianego związku).

Plusy:

  • jest to standard C bez rozszerzeÅ„
  • przepÅ‚yw sterowania jest proste
  • to wÅ‚aÅ›ciwie (jakoÅ›) mniej gadatliwe niż __attribute__ __cleanup__

Wady:

  • nie zapewnia "peÅ‚nego" RAII (tzn. nie chroni przed goto lub wyjÄ…tkami C++: {[10] } jest zwykle implementowany z maszynami C++ pod maskÄ…, wiÄ™c jest bardziej kompletny). Bardziej poważnie, nie chroni przed wczesnym return (DziÄ™ki @ Voo). (You can conajmniej guard against a missplaced break - if you want to-by adding a third line, switch (0) default: to the end of M_AROUND_BLOCK2.)
  • nie wszyscy zgadzajÄ… siÄ™ z makrami rozszerzajÄ…cymi skÅ‚adniÄ™ (ale weź pod uwagÄ™, że sÄ… rozszerzajÄ…cymi semantykÄ™ C tutaj, wiÄ™c...)
 18
Author: Leushenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 11:04:46

Wiem, że to nie jest to, co chcesz usłyszeć, ale namawiam cię, żebyś tego nie robił.

Jest to całkowicie dopuszczalne w stylu C, aby mieć jeden punkt powrotu, przed którym wszystko jest sprzątane. Ponieważ nie ma wyjątków, jest to łatwe do zrobienia i łatwe do zweryfikowania, patrząc na funkcję.

Używanie makro-hackery lub kompilatora "features"w tym celu nie jest akceptowane w stylu C. Będzie to ciężar dla wszystkich po przeczytaniu i zrozumieniu. I w końcu nie zyskasz much.

 4
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 10:06:41