Implementieren von RAII in reinem C?

Lesezeit: 9 Minuten

Benutzeravatar von elifiner
Eliminierer

Ist eine Umsetzung möglich RAII in reinem C?

Ich nehme an, es ist auf keine vernünftige Weise möglich, aber vielleicht ist es mit einer Art schmutzigem Trick möglich. Überlastung des Standards free Funktion in den Sinn kommt oder vielleicht die Rückgabeadresse auf dem Stack überschreiben, so dass, wenn die Funktion zurückkehrt, sie eine andere Funktion aufruft, die irgendwie Ressourcen freigibt? Oder vielleicht mit einem setjmp/longjmp-Trick?

Dies ist von rein akademischem Interesse und ich habe nicht die Absicht, solch einen nicht tragbaren und verrückten Code zu schreiben, aber ich frage mich, ob das überhaupt möglich ist.

  • Sie können die Rücksendeadresse auf dem Stack nicht einfach überschreiben; Sie müssen den Wert bei der Eingabe beibehalten und ihn dann mit einer Alternative überschreiben. Hässlich, aber möglicherweise effektiv. Erwägen Sie die Verwendung einer Arena-basierten Speicherzuweisung für den Speicher. Andernfalls seien Sie einfach sehr vorsichtig (und machen Sie sich Sorgen um Interrupts!).

    – Jonathan Leffler

    15. Dezember 2008 um 15:37 Uhr

  • Ist RAII ohne Ausnahmen so nützlich? (nur Fragen)

    – Josh Petitt

    7. Juni 2013 um 21:49 Uhr

  • @JoshPetitt sicher, vorzeitige Rückkehr und einfach nicht daran denken zu müssen, jedes einzelne Ding zu befreien = weniger Fehler.

    – Oktalist

    7. Juni 2013 um 22:14 Uhr

  • @JoshPetitt du musst wenigstens eine Erklärung weniger schreiben. zB fopen ohne entsprechendes fclose

    – Justin Meiner

    11. Juli 2013 um 2:13 Uhr

  • Ich bin überrascht, dass niemand vorgeschlagen hat, einen C++-Compiler zu verwenden und in diesem obskuren C-Dialekt zu schreiben, der von C++ kompiliert werden kann (einfach RAII-Funktionen verwenden, wenn Sie sie wollen). Ich bin auch überrascht, dass Sie die Antwort von Johannes nicht akzeptiert haben, es sei denn, Sie streben nach einer “allgemeineren” Lösung.

    – jxh

    17. Februar 2017 um 18:13 Uhr

Dies ist inhärent implementierungsabhängig, da der Standard eine solche Möglichkeit nicht enthält. Für GCC ist die cleanup Das Attribut führt eine Funktion aus, wenn eine Variable den Gültigkeitsbereich verlässt:

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

Drucke:

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

Sehen hier

  • Das ist viel ordentlicher, als ich es für möglich gehalten hätte!

    – Eliminierer

    15. Dezember 2008 um 16:23 Uhr

  • Zur Information, diese Funktion wird mindestens ab GCC 4.0.0 unterstützt

    – Penghe Geng

    6. Juni 2019 um 14:28 Uhr

  • Dies scheint auch in Clang 11.0.0 zu funktionieren, obwohl es nicht in der Attributreferenz aufgeführt ist.

    – jb

    4. März 2020 um 9:14 Uhr

  • ⁺¹. Hinweis: Sie können die Variable in derselben Zeile initialisieren, in der sie deklariert wurde.

    – Hallo Engel

    4. März 2021 um 15:04 Uhr

Benutzeravatar von Keldon Alleyne
Keldon Alleyne

Eine Lösung, um RAII auf C zu bringen (wenn Sie keine haben cleanup()) besteht darin, Ihren Funktionsaufruf mit Code zu umschließen, der eine Bereinigung durchführt. Dies kann auch in ein ordentliches Makro verpackt werden (am Ende gezeigt).

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

Sie können den gesamten Textbausteincode in ausdrücken SomeFunction mit Makros, da es bei jedem Anruf gleich ist.

Zum Beispiel:

/* 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);
  /* ... */
}

Hinweis: Sie sollten ein erweitertes Makro-Framework wie P99 verwenden, um so etwas wie das oben Gesagte zu ermöglichen.

  • Eine Methode explizit aufrufen müssen (RaiiDestroyAll) geht irgendwie gegen die Idee von raii.

    – Muhende Ente

    24. Oktober 2018 um 17:14 Uhr

  • Dies ist der Mechanismus durch die Sprache. Wenn Sie möchten, können Sie den expliziten Aufruf mit Makros ausblenden, z. RTN_RAII(int, func_name, int, arg0, int, arg1, {/* code */}) (Sie können P99 verwenden, um Makros schwer zu heben).

    – Keldon Alleyne

    31. Oktober 2018 um 14:33 Uhr

  • en.cppreference.com/w/cpp/language/raii „Ein anderer Name für diese Technik ist Scope-Bound Resource Management (SBRM), nach dem grundlegenden Anwendungsfall, bei dem die Lebensdauer eines RAII-Objekts aufgrund des Scope-Exits endet.“ Bjarne Stroustrup sagte: “RAII ist ein schlechter Name für das Konzept … Ein besserer Name ist wahrscheinlich: Constructor Acquires, Destructor Releases.” Der Punkt ist, dass die Freigabe automatisch erfolgt, egal was passiert. Der Punkt ist, dass Sie keinen Reinigungsanruf tätigen sollten. Das ist die Definition von RAII. Ich habe gehört, dass einige C-Compiler etwas Ähnliches als Erweiterung anbieten, aber C selbst kann das nicht.

    – Muhende Ente

    31. Oktober 2018 um 17:06 Uhr

  • Offensichtlich kann C keine automatische bereichsbasierte Bereinigung durchführen. Aus der Frage: “Ich nehme an, es ist auf keine vernünftige Weise möglich, aber vielleicht ist es mit einer Art schmutzigem Trick möglich”. Was ich bereitgestellt habe, ist ein Mechanismus als Problemumgehung, mit dem Sie erhalten können gelang es Bereinigung in C mit Hilfe eines einfachen Makros, bei dem Sie “destroy” oder “cleanup” nicht manuell aufrufen müssen (die Bereinigung erfolgt im Makro), sondern Sie müssen die Zeiger registrieren.

    – Keldon Alleyne

    31. Oktober 2018 um 19:09 Uhr

  • Sogar C++ erfordert, dass Sie das Ende des Bereichs irgendwie markieren, normalerweise mit einer engen Klammer. Es scheint mir, dass RaiiDestroyAll() lediglich den gleichen Zweck erfüllt, außer dass es von der schließenden geschweiften Klammer entkoppelt ist. Man könnte sie mit einem Makro neu koppeln, aber ich denke nicht, dass das die Dinge schöner machen würde: Es ist etwas von der Essenz von C++ und C, Implementierungsdetails zu verstecken bzw. nicht zu verstecken, also scheint mir das passend zu sein Ansatz (und kein schmutziger Trick) zu RAII in C. Und getrennte Klammern und RAII-Bereiche lassen Sie Dinge tun, wie zB die Verwendung eines RAII-Bereichs über mehreren Klammern.

    – michaeljt

    23. November 2018 um 16:36 Uhr


Wenn Ihr Compiler C99 (oder sogar einen wesentlichen Teil davon) unterstützt, können Sie ein Array mit variabler Länge (VLA) verwenden, wie zum Beispiel:

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

    // ...
}

Wenn die Erinnerung dient, gcc hatte/unterstützte diese Funktion lange bevor sie zu C99 hinzugefügt wurde. Dies entspricht (ungefähr) dem einfachen Fall von:

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

Sie können jedoch keine der anderen Dinge tun, die ein dtor tun kann, wie z. B. das Schließen von Dateien, Datenbankverbindungen usw.

  • Beachten Sie, dass 1) der Stack normalerweise viel begrenzter ist als der Heap; 2) Sie können sich grundsätzlich nicht von einem Stapelüberlauf erholen (Sie erhalten ein SIGSEGV, mit dem Sie nicht umgehen können). Ein fehlgeschlagener malloc gibt nullptr zurück, während ein fehlgeschlagener new std::bad_alloc auslöst.

    – Raúl Salinas-Monteagudo

    28. Februar 2018 um 12:13 Uhr

  • @RaúlSalinas-Monteagudo was meinst du mit “viel begrenzter”?

    – jb

    4. März 2020 um 9:04 Uhr

  • @jb: Abhängig vom Betriebssystem ist die Größe des Stacks oft auf nur wenige Megabyte oder so begrenzt. Aber er hat nur halb recht. Nur zum Beispiel führt unter Linux ein fehlgeschlagener malloc (oder ein neuer) häufig dazu, dass der OOMKILLER ausgeführt wird, der das betreffende Programm einfach beenden kann (oder andere Programme beenden kann, um genügend Speicher freizugeben, damit die Zuweisung erfolgreich ist). Obwohl der Heap oft größer ist, kann der Versuch, mehr als verfügbar zu verwenden, ebenfalls nicht wiederherstellbar sein.

    – Jerry Sarg

    5. März 2020 um 0:40 Uhr


  • @jb: Wenn ich mich richtig erinnere, beträgt die Stapelgröße (standardmäßig) 8 MB für einen normalen Prozess und 2 MB für einen Thread. Wenn Sie anfangen, Arrays im Stack zu erstellen, kann es passieren, dass Sie ihn überlaufen. Aber das hängt natürlich von der Art Ihres Datums ab und davon, wie tief Sie Aufrufe verschachteln. Für ein zuverlässiges Programm würde ich solche Gefahren nicht eingehen.

    – Raúl Salinas-Monteagudo

    10. März 2020 um 8:07 Uhr

  • @RaúlSalinas-Monteagudo gute Punkte. 8 MB (oder sogar 2 MB) sind eigentlich ziemlich viel Speicher für die meisten Anwendungen und ich muss zugeben, dass ich noch nie auf Probleme gestoßen bin. Ich habe jedoch gerade ein einfaches Testprogramm geschrieben und konnte es sofort zum Absturz bringen, indem ich ein 10-MB-Array auf dem Stapel zugewiesen habe. Werde das auf jeden Fall für die Zukunft im Hinterkopf behalten. Vielen Dank!

    – jb

    10. März 2020 um 8:41 Uhr

Benutzeravatar von Jasper Bekkers
Jasper Becker

Der wahrscheinlich einfachste Weg ist, goto zu verwenden, um am Ende einer Funktion zu einem Label zu springen, aber das ist wahrscheinlich zu manuell für die Art von Dingen, die Sie sich ansehen.

Ich würde mich dafür entscheiden, die Absenderadresse auf dem Stapel zu überschreiben. Es würde am transparentesten funktionieren. Ersetzen free funktioniert nur mit Heap-zugewiesenen “Objekten”.

Benutzeravatar von nelix
Nelix

Hast du dir alloca() angeschaut? Es wird freigegeben, wenn eine Variable den Gültigkeitsbereich verlässt. Aber um es effektiv zu nutzen, muss der Aufrufer immer alloca ausführen, bevor er es an Dinge sendet … Wenn Sie strdup implementieren, nun, Sie können alloca nicht verwenden.

Um diesen Teil von Johannes ‘Antwort zu ergänzen:

Das cleanup-Attribut führt eine Funktion aus, wenn eine Variable den Gültigkeitsbereich verlässt

Es gibt eine Einschränkung für das Bereinigungsattribut (http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Variable-Attributes.html): Dieses Attribut kann nur auf Auto-Funktionsbereichsvariablen angewendet werden.

Wenn es also eine statische Variable in einer Datei gibt, ist es möglich, RAII für eine statische Variable auf diese Weise zu implementieren:

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

Das ist ein Test:

>./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

1417870cookie-checkImplementieren von RAII in reinem C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy