Wann prüft ihr Speicherallokierungen?
-
Hallo,
die Frage steht ja eigentlich schon im Titel. Mich würde mal interessieren wann/ob ihr Speicherallokierungen wie z.B.
new/malloc/vector.push_backoder vielleicht sogar so etwas
std::string str1 = "Hallo"; std::string str2; str1 = str2;über try/Rückgabewerte prüft oder absichert?
-
Wenn nicht mehr genügend Speicher für Strings und dergleichen zur Verfügung steht, ist das System höchstwahrscheinlich nicht mehr in einem Zustand, in dem es eine Fehlermeldung anzuzeigen in der Lage wäre. Falls doch, werden Exceptions in meinen Programmen aber ohnehin durch die VCL gefangen.
Falls man sehr große Objekte alloziert, sollte man das allerdings tun.
-
new wirft bei zu wenig Speicher eine std::bad_alloc Exception. Und da std::string intern sicherlich auch new aufruft, wird somit auch hier std::bad_alloc geworfen.
-
audacia schrieb:
Wenn nicht mehr genügend Speicher für Strings und dergleichen zur Verfügung steht, ist das System höchstwahrscheinlich nicht mehr in einem Zustand, in dem es eine Fehlermeldung anzuzeigen in der Lage wäre. Falls doch, werden Exceptions in meinen Programmen aber ohnehin durch die VCL gefangen.
Falls man sehr große Objekte alloziert, sollte man das allerdings tun.
Hmm meine Anwendung kommt in genau so einem Zustand zum Einsatz, dann wenn andere Programme z.B. wegen zu wenig Speicher abstürzen. Muss ich mal schauen wie ich das mache.
Artchi schrieb:
new wirft bei zu wenig Speicher eine std::bad_alloc Exception. Und da std::string intern sicherlich auch new aufruft, wird somit auch hier std::bad_alloc geworfen.
Mir ist schon klar das, oder was für Exceptions geworfen werden. Ich wollte wissen ob ihr diese immer direkt abfangt.
-
heutzutage musst du nicht mehr auf allokierung prüfen, das hat einfach keinen sinn mehr, bei dem heutigen arbeitsspeicher. Wozu sollte man das auch testen? wenn kein speicher mehr da ist, ist sowieso ende: Exception und gut.
-
Ah da spricht mir jemand aus der Seele!
Wenn ich sonst soetwas immer mache, dann heißt es schlechter stil etc.
Nur ich glaube nicht, dass meine konsolenprogramme mehr als 1GB Arbeitsspeicher verbrauchen, und wenn der Benutzer zu viele Programme offen hat, dann ist mein Programm auch gleich wieder weg...
-
Man MUSS sich immer Gedanken machen, wie das Programm reagiert, wenn ein Fehlerzustand auftritt. Wenn man Speicher mit malloc anfordert, MUSS man abfragen, ob der Speicher auch geliefert wurde. Genau so muss man auch jeden möglichen Fehler jeder aufgerufenen Funktion berücksichtigen.
C++ macht die Sache einfacher. Wie bereits erwähnt wird bei new eine Exception geworfen. Aber auch das ist noch nicht die Endgültige Lösung. Der Code muss darauf vorbereitet sein. Ich muss die Exception nicht sofort fangen, sondern da, wo ich sinnvoll darauf reagieren kann. Und der Code muss exceptionsafe sein, so dass bei einem solchen unplanmässigen Ausstieg keine Resourcenleaks entstehen. Also alle Aufräumarbeiten im Destruktor erledigen und alle Objekte, die dynamisch angefordert werden, durch Stackobjekte, wie z. B. Smart-pointer absichern.
Wenn die Fehlerbehandlung über Exceptions erfolgt, ist die Sache doch gar nicht so schwer. Man bedenke, dass häufig nach einer Exception der eine oder andere Destruktor aufgerufen wird, welches so viel Speicher frei geben kann, dass zumindest eine Fehlermeldung ausgegeben werden kann. Möglicherweise ist dann sogar eine Weiterarbeit möglich. Wenn nicht, dann hat das Programm wenigstens der Aussenwelt mitgeteilt, dass er seine Aufgabe aus Speichermangel nicht verrichten konnte. Das ist dann kein Programmabsturz, sondern ein einfacher Programmabbruch aufgrund einer Fehlersituation. In jedem Fall hat jeder Prozess ein Rückgabewert, welcher selbst bei Speichermangel gefüllt werden kann.
Meine main-Funktion enthält immer einen try-catch-Block, welcher eine std::exception abfängt und die gelieferte Fehlermeldung auf cerr ausgibt. Und alle Funktionen, die ich aufrufe, die nicht selbst exceptions werfen, werden in einen wrapper eingebaut, welcher die Fehlerrückgaben in Exceptions übersetzt. Eine korrekte main-Funktion sieht etwa so aus:
int main(int argc, char* argv[]) { try { // hier kommt mein Code rein } catch (const std::exception& e) { std::cerr << e.what() std::endl; return 1; } }Die Welt ist leider voll von Programmen, die nicht nach dem Prinzip entwickelt wurden. Daher ist es für uns schon selbstverständlich, dass ein Programm bei zu wenig Speicher abstürzt. Ein Absturz ist immer ein Fehler im Programm, auch wenn die Ursache zu wenig Speicher ist.
Tntnet
-
@tntnet
tja, da gibt es nur ein Problem, was leider nie richtig beachtet wird: Wenn man in den catch-Block kommt wurde noch kein Speicher freigegeben. Daher die Frage: Wie willst du den Text der Exception ausgeben, wenn doch kein Speicher mehr da ist?
Wenn diese Exception geworfen wurde, kannst Du nicht mal mehr ne Datei öffnen, da nicht mal mehr Speicher für ein Handle da ist. Also solltest Du vorher Speicher freigeben, bevor du ausgibst. Problem ist hier, da man ggf. nicht mehr in dem Scope ist, wo der Zeiger existiert um ihn freizugeben.
Ergo: Dein Code bringt nichts.
-
Deswegen kapselt man in C++ den Code, welches ne Exception schmeisst in ne Classe oder Struct :o
-
kenner des new schrieb:
@tntnet
tja, da gibt es nur ein Problem, was leider nie richtig beachtet wird: Wenn man in den catch-Block kommt wurde noch kein Speicher freigegeben. Daher die Frage: Wie willst du den Text der Exception ausgeben, wenn doch kein Speicher mehr da ist?
Wenn diese Exception geworfen wurde, kannst Du nicht mal mehr ne Datei öffnen, da nicht mal mehr Speicher für ein Handle da ist. Also solltest Du vorher Speicher freigeben, bevor du ausgibst. Problem ist hier, da man ggf. nicht mehr in dem Scope ist, wo der Zeiger existiert um ihn freizugeben.
Ergo: Dein Code bringt nichts.
Ha ha ha! Hast du mal genauer nachgedacht? Du gehst davon aus das nur dann ein bad_alloc kommt, wenn absolut kein Speicher mehr frei ist. Aber ein Speichermangel kann auch dann auftreten, wenn z.B. noch 1 MByte frei ist. Denn wenn ich 2 MByte anfordere, nur noch 1 MByte frei ist, bekomme ich logischerweise ein bad_alloc. Aber deshalb kann ich noch locker z.B. noch eine Logdatei aufmachen oder sogar eine Fehler-Mail mit Loginfos an den Support schicken. Eben weil ich noch 1 MByte frei habe.
Ihr denkt nämlich nur daran, das ein bad_alloc kommt, wenn schon kein Speicher mehr frei ist. Das kann auch passieren, das heißt aber noch lange nicht, das ich deshalb die Fehlerbehandlung vernachlässigen kann. Weil das ebend nur ein Fall von viel sein kann. Kein Wunder das es soviel schlechte Software gibt, wenn alle so kurzsichtig sind.
-
Artchi schrieb:
Kein Wunder das es soviel schlechte Software gibt, wenn alle so kurzsichtig sind.
ha ha ha mal zurück. Du nennst das Kurzsichtig, aber gehst hier von "Auf gut Glück" aus, indem du sagst, das man ja nur riese Blöck allokiert und keine kleinen?
Klar, wenn das immer so ist, ist es möglich noch zu handel - aber es ist nicht immer so. Also muss deine Software ja bei diesem "Auf gut Glück" ja noch schlechter sein, da sie so kurzsichtig gehalten ist.
Tja, dumm gelaufen.
Achja, ich allokier dann grad mal 32 Byte für nen String und keine 2 MByte wie Du für 31 Zeichen + eine Nullterminierung.
-
Die Sache ist ja eher die. Wenn für das Allokieren eines Strings nicht einmal mehr ein paar Bytes übrig sind, wird weder der Prozess noch das OS zum Ausgeben irgendeiner Meldung im Stande sein. Wenn aber von den angeforderten 2 MB nur 1 MB zu Verfügung steht, könnte man noch eine Fehlermeldung rausbringen und das Programm "sauber" beenden.
Für mich würde ich dann folgende Schlüsse ziehen:
Prüfe die Allokierung bei besonders großen Allokierungen oder bei Allokierungen deren Größe von Benutzereingaben abhängt.
Prüfe die Allokierungen nicht, wenn es nur kleine (interne) sind.Ein weiterer Ansatz wäre z.B. ganz am Anfang des catch Blocks Speicher freizugeben der beim Start des Programmes allokiert/reserviert wurde, damit die Fehlerbehandlung korrekt arbeiten kann.
-
kenner des new schrieb:
Artchi schrieb:
Kein Wunder das es soviel schlechte Software gibt, wenn alle so kurzsichtig sind.
ha ha ha mal zurück. Du nennst das Kurzsichtig, aber gehst hier von "Auf gut Glück" aus, indem du sagst, das man ja nur riese Blöck allokiert und keine kleinen?
Klar, wenn das immer so ist, ist es möglich noch zu handel - aber es ist nicht immer so. Also muss deine Software ja bei diesem "Auf gut Glück" ja noch schlechter sein, da sie so kurzsichtig gehalten ist.
Tja, dumm gelaufen.
Achja, ich allokier dann grad mal 32 Byte für nen String und keine 2 MByte wie Du für 31 Zeichen + eine Nullterminierung.
Ich darf mich zitieren?
Artchi schrieb:
wenn schon kein Speicher mehr frei ist. Das kann auch passieren,
Lies mal mein Posting richtig.
Und ganz am Ende ist es völlig egal! Man muß Fehlerbehandlung in seine Programme einbauen. Jeder der das wissentlich nicht macht (was du hier forderst!) ist vorsätzlich fahrlässig!
-
Sehr lesenswert (besonders für die überzeugten "wenn kein speicher mehr da ist fliegt bad_alloc, also kann man das problem leicht behandeln"-leute): http://www.gotw.ca/publications/mill16.htm
Die Praxis ist halt nicht immer so nett.

-
Der Artikel ist aus dem Jahr 2001. Heute sieht die Sache schon wieder etwas anders aus.
Auf Systemen mit 32 Bit Adressraum ist es kein Problem soviel physikalischen Speicher zu haben dass ein Prozess alleine garnie soviel Speicher anfordern kann dass es irgendwo zu paging kommt. 4GB kosten schliesslich kein Vermögen mehr (ich hab 4 GB in meinem privaten PC).
Auf so einem System wird es also nicht zu viel paging kommen, egal wieviel Speicher man anfordert. Auch das Verhalten von Linux, Speicher "herzuggeben" der u.U. garnicht committed werden kann, ist hier kein Problem, da es eigentlich nicht dazu kommen kann (solange der PC 4 GB Speicher und noch ein paar GB Swap zur Verfügung hat).Auf Systemen mit 64 Bit Adressraum sieht es wieder eher so aus wie in dem Artikel beschrieben.
Davon abgesehen: man *kann* Programme so schreiben dass sie an jeder Stelle mit "new failure" klarkommen, es erfordert nur etwas an Aufwand. Wie in dem Artikel beschrieben ist es möglich (wenn auch oft nicht einfach) sämtliche Resourcen die benötigt werden um Fehler zu behandeln zu Beginn Anzufordern, so dass sie jederzeit zur Verfügung stehen wenn man sie braucht. Ob das für ein Programm sinn macht oder nicht kommt wohl sehr darauf an was für ein Programm man schreibt.
Für irgendwelche Programme mit denen ein User direkt arbeitet ist das wohl nur in einigen wenigen Fällen angebracht, z.B. wenn der User ein Dokument laden will das dann nichtmehr in den Speicher passt, oder er eine Operation anwirft die viel Speicher braucht (Spline-Patches in ein Mesh umwandeln kann sehr viel Speicher brauchen - nur um ein Beispiel zu nennen). Schliesslich wäre ich als User ziemlich sauer wenn ich mit einem Programm arbeite und mir das irgendwann einfach abbricht ohne mir eine Chance zu geben meine bisherige Arbeit zu sichern.
Für Programme mit denen kein User "direkt" arbeitet (z.B. Server Programme) finde ich sollte man allerdings etwas andere Standards anwenden. Z.B. ein Datenbank Server der nicht darauf achtet wieviel Speicher er anfordert, bzw. der den Fall "kein Speicher mehr da" nicht gut behandelt ist IMO unbrauchbarer Mist.
Natürlich spielt zumindest ein Punkt den Herb Sutter in dem Artikel erwähnt hier auch eine Rolle, nämlich dass es einen genauso umbringen kann wenn das OS anfängt wild zu pagen weil ein Programm zuviel Speicher verwendet. Ein Programm das mit 1/100 (oder auch nur 1/10) seiner "Original-Gescchwindigkeit" läuft ist effektiv "tot". Bei Programmen wie Datenbank Servern (sorry, mir fällt kein anderes gutes Beispiel ein), die eigentlich nie genug Speicher haben können und auch 16 oder 32 GB schnell "voll bekommen", muss man eben den max. Speicherverbrauch des Programmes anders begrenzen. Z.B. indem man den User/Admin/... einstellen lässt wieviel Speicher das Programm maximal verwenden darf (MS SQL Server macht das z.B. so). Das führt aber nicht dazu dass man sich um "bad_alloc" (bzw. allgemein den Fall "kein Speicher mehr da") nichtmehr kümmern muss, sondern führt nur zusätzlich den Fall "keine Speicheranforderung mehr erlaubt" ein.
----
Eine andere Sache die ich noch erwähnen möchte: Selbst wenn man ein Programm schreibt wo es akzeptabel ist wenn ein "bad_alloc" erst in main() (oder garnicht) gefangen wird sollte man beachten dass "bad_alloc" geworfen werden kann, und die entsprechenden Stellen "exception safe" programmieren. Tut man das nicht, könnte es passieren dass beim stack unwinding Dinge passieren die man so garnicht haben möchte.
-
Also Antworten wie "heutzutage braucht man sowas nicht mehr" sind ja wohl völlig daneben. Erstens gibts genug Embedded Anwendungen, die keine GB an Speicher bekommen und vielleicht nichtmal swappen können. Zweitens gibts durchaus Applikationen auf X86-Hardware, die ne Menge Speicher am Stück brauchen. Bei uns gibts z.B. ne CT-Software, die 1.5 GB Speicher in 18s vollknallt. Schön blöd wenn man da nen Absturz verursacht nur weil keine 1.5 GB allokiert werden konnten. Und was Fehlermeldungen anbelang, da kann man ja am Anfang des Programms eine Stück Speicher allokieren und im catch dann verwenden umd entsprechende Meldungen an den User auszugeben und Ressourcen zu sichern.
-
yogle schrieb:
... Exceptions ...immer direkt abfangt.
1.) "Pauschalisierungen sind IMMER falsch !",
sagte meine Deutschlehrerin
Hier konkret: Es ist ja gerade der große Vorteil von Exceptions gegenüber return codes, dass man sie nicht "immer direkt" behandelt, sondern nur da behandelt (und soweit fliegen lässt), wo man auch sinnvoll darauf reagieren kann.2.) Kapselung ist Dein Freund
Du musst Dich nur mit der Schnittstelle des genutzten Klassen/Funktionen (und dazu zählt eben auch das Verhalten in einem Fehlerfall) beschäftigen.
Wenn Du string/vector nutzt, brauchst Du nicht zu wissen, ob/wann die intern Speicher anfordern - sie werden Dir schon mitteilen, wann immer ihnen etwas nicht schmeckt. Vielleicht "biegen" sie das ja auch selbst wieder hin und Du merkst gar nichts davon.... auf jeden Fall sollten Klassen/Funktionen so gestrickt sein, dass man nicht hinter ihre Kulissen zu schauen braucht (und string/vector/new/... sind so).3.) Eine Ausnahme ist eine Ausnahme
Exceptionhandling ist eine seeeehr komplexe Angelegenheit, weil man oft kaum feststellen kann, was noch "funktioniert" und was nicht. Wie oben schon gesagt: Wenn kein Speicher mehr für die Anwendung zur Verfügung steht, kann es sein, dass für ausgiebige "Klimmzüge" gar kein Speicher mehr da ist.
Andererseits ist "Speicher alle" bei modernen Betriebssystemen ein sehr schwammiges Konzept. Klar, wenn ich einen fetten Progerammierfehler habe (und deswegen 17 PetaByte allokieren will), wird das noch ziemlich "gemeldet". Aber in Zeiten von "virtuellen Speicher", "swapping", .... gibt's eigentlich kaum noch bad_allocs. Da geht Dir schon viel früher schon die Performance derart in den Keller, dass das dann zum eigentlichen Problem wird.
Wenn Du natürlich auf einer speziellen Plattorm (Micro-Controller etc.) arbeitest, die sehr viel simplere Speichermechanismen und geringen Platz hat, kann das anders aussehen. Sowas schreit aber auch schnell nach einer speziellen Speicherverwaltung...
Auch ich verweise hier nochmal auf Sutters Artikel: http://www.gotw.ca/publications/mill16.htm"There's Often Little Point in Checking for New Failure Anyway"
...
1. Checking new failure is useless on systems that don't commit memory until the memory is used.
...
2. In the real world, new failure is a rare beast, made nearly extinct by the thrashing beast.
...
3. There's not always much you can do when you detect new failure.Gruß,
Simon2.
-
Aber in Zeiten von "virtuellen Speicher", "swapping", .... gibt's eigentlich kaum noch bad_allocs
Auf 32 Bit Systemen hast du dein bad_alloc schneller als du denkst. Ganz ohne Swapping etc.
Glaubs mir einfach.
-
hustbaer schrieb:
Aber in Zeiten von "virtuellen Speicher", "swapping", .... gibt's eigentlich kaum noch bad_allocs
Auf 32 Bit Systemen hast du dein bad_alloc schneller als du denkst. Ganz ohne Swapping etc.
Glaubs mir einfach.beweise? glauben kannst du in der kirche, hier zählen nur fakten.
-
Das kannst du direkt ausprobieren. Allokiere einfach solange 100MB Große Blöcke unter beispielsweise WinXP bis es kracht.
Aber das funktioniert halt nicht auf allen Plattformen. Neuere Betriebssysteme mit 64bittigem Adressraum haben halt deutlich mehr virtuellen als physischen Speicher...
-
Hi,
unabhängig, ob etwas guter Stil ist, und ob man so was macht...
Die meisten Programme, die wir hier so schreiben haben irgend was mit der Arbeit anderer Leute (oder unserer eigenen) zu tun. Da nützt ein Abpfiff ohne jeden Kommentar herzlich wenig. Wenn ich noch irgendwie gesagt bekomme, daß der Speicher alle ist, dann kann ich als Nutzer was tun, merh Swap, andere Prozesse beenden, anderen Rechner nehmen oder einfach noch ein paar Maikäfer im Rechner nachrüsten. Wenn ich es aber nicht erfahre, dann weiß ich nur es geht nicht, obwohl es vielleicht mit ein paaar "Bitchen" mehr gehen würde. Also ist das Programm für mich erst mal gestorben.
Sicher gibt es Programe, wo ein Absturz keinen Schaden tut, aber das ist nicht die Regel. Meist hat man entweder schon eine Menge eingegeben, oder es sind Dateien offen oder...
Das wichtigste wofür ich als Programmierer Sorge tragen muß, ist daß mein Programm keinen Schaden anrichtet. Also zumindest offene Dateien schließen. Wenn machbar, dann auch eventuell eingegebene Daten in eine temporäre Datei speichern, oder unvollständig geäderte Daten verwerfen statt sie unkonsistent zu speichern.
Das hängt immer davon ab, was die Daten an denen das Programm arbeitet wert sind. Wenn es nur eine Stunde Arbeit ist, ist es die eine Sache, aber man muß IMMER davon ausgehen, daß es noch Kunden gibt, die keine Datensicherung kennen. Wenn dann die gesammten Geschäftsgrundlagen eines kleinen Betriebes den Bach runter sind, dann hilft der Hinweis auf Datensicherungen wenig. Da können im schlimmsten Fall Arbeitsplätze unbeteiligter dritter dranhängen.
Also auf den Punkt gebracht zumindest immer versuchen über Ressourcenknappheit zu informieren, und je nach Wert der zu bearbeitenden Daten gegebenenfalls einen Notfallplan abarbeiten, der das schlimmste verhindert.
Für eine Meldung sollte der Speicher fast immer reichen, da durch das bei einer Exception stattfindende stack unwinding ja auch wieder speicher frei wird. Aber das ist ja auch nicht das Problem. Wo ist die Schwierigkeit mal ein kleies Programm zu schreiben, dem man sagen kann wieviel Platz es belegen soll und das dann bei abgeschaltetem swap parallel starten, den Platz allemachen und gucken was passiert.Wesentlich weniger schlimm finde ich die Verluste durch nicht wieder freigegebenen Speicher, wo z.B. Myers so viel drüber schreibt. Solange man nicht regelmäßig was vergißt tut das bei den heutigen Systemen aus meiner sicht fast gar nichts. Wenn irgend eine Seite nicht mehr benutzt wird, wird sie irgendwann sowieso vom System ausgelagert und liegt dann bis zum nächsten Neustart im swap. Wenns die Regel wird, oder bei sehr großen Speicherblöcken passiert ists aber auch da nicht schön. Aber auf keinen Fall en Argument gegen C++ wie es manche anführen, weil es da keine automatische Speicherbereinigung gibt. Aber mit sorgfältiger Programiereung und eventuell mal durch die Lappüen rutschenden Blocks denke ich kann man leben.
Gruß Steffen das Mümmel