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
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
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.
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
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”.
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
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
14178700cookie-checkImplementieren von RAII in reinem C?yes
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