STL-Container bad_alloc?



  • Hallo!
    Ich wollte mal fragen, ob die STL-Container beim Hinzufügen eines Elements
    auch bad_alloc werfen, wenn new.h eingebunden wird?
    Z. B. wenn ich push_back auf einen Vector anwende, und kein Speicher mehr
    frei ist, oder auch insert bei einer Liste.
    Gibt es eine Möglichkeit, Speichermangel zu simulieren, um zu testen, ob
    mein Exceptions-Handling so funktioniert, wie es soll?
    Ich programmiere unter Linux mit gcc und Eclipse.



  • Hi,

    naja, Du kannst ka "Monsterobjekte" nehmen ...

    struct BigClass {
       char x[100000000000];
    };
    
    vector<BigClass> v;
    ....
    

    ... aber ich würde mich nicht allzu sehr darauf versteifen. Nach Herb Sutter, funktioniert mit modernen Betriebssystemen oftmals das "bad_alloc" nicht so, wie man das denkt - und wenn man dann doch ein bad_alloc bekommt, funktioniert meist nichts Anderes mehr, so dass die Information (bzw. die Tests) nicht besonders hilfreich ist (sind).

    Gruß,

    Simon2.



  • Naja, ob das eine so gute Idee ist, den Speicher wirklich "zuzumüllen"?
    Ich hatte daran gedacht, dem auszuführenden Programm nur wenig Speicher
    zuzuweisen, dass die IDE und alles weitere noch vernünftig läuft.
    Aber kann ich davon ausgehen, dass die Container einen bad_alloc werfen?



  • mase schrieb:

    Aber kann ich davon ausgehen, dass die Container einen bad_alloc werfen?

    Ja.



  • mase schrieb:

    Naja, ob das eine so gute Idee ist, den Speicher wirklich "zuzumüllen"?
    Ich hatte daran gedacht, dem auszuführenden Programm nur wenig Speicher
    zuzuweisen, dass die IDE und alles weitere noch vernünftig läuft....

    Äh ... worin liegt denn nun der Unterschied ?
    Wenn Du ein Betriebssystemmittel in der Hand hast, um Deinen Prozess geeignet zu isolieren - prima ! Aber der Weg wäre doch derselbe, oder ? Du füllst einen Container mit vielen/großen Objekten, bis das Betriebssystem eine weitere Speicheranforderung der Runtime abweist....

    Mir ist inzwischen noch eingefallen: Du kannst einen eigenen Allokator für Deine Klasse schreiben und den sagen, dass er z.B. beim 50. Aufruf einen bad_alloc schmeisst (wie das IIRC der Standardallokator im Fall des Falles auch tut) ... dann siehst Du, ob der STL-Container sie durchleitet bzw. wie DEin Programm darauf reagiert.

    Gruß,

    Simon2.



  • Ich wollte das ganze mal mit einem kleinen Programm testen:

    #include <iostream>
    #include <list>
    #include <new>
    
    using namespace std;
    
    int main()
    {
    	list<char> Liste;
    	unsigned long i = 1;
    	while (i != 0)
    	{
    		try
    		{
    			Liste.push_back('x');
    			i++;
    		}
    		catch (bad_alloc Ausnahme)
    		{
    			cout << "Kein Speicher nach " << i << " Einträgen!";
    			break;
    		}
    	}
    }
    

    Ich hab also einfach eine Endlosschleife erstellt und fülle die Liste.
    Ich hab das ganze unter Linux getestet.
    Zuerst läuft der RAM voll, dann Swap, dann hängt sich die ganze Maschine auf.
    Keine bad_alloc Ausnahme.
    Oder hab ich bei meinem Testprogramm was falsch gemacht? Ich hab vorsichts-
    halber mal <new> eingebunden.
    Auch ein catch (...) brachte nix.



  • mase schrieb:

    Ich wollte das ganze mal mit einem kleinen Programm testen:
    ...
    Keine bad_alloc Ausnahme.
    ...

    🙄 *seufz*
    Genau das habe ich bereits mit

    Simon2 schrieb:

    ...
    ... aber ich würde mich nicht allzu sehr darauf versteifen. Nach Herb Sutter, funktioniert mit modernen Betriebssystemen oftmals das "bad_alloc" nicht so, wie man das denkt - und wenn man dann doch ein bad_alloc bekommt, funktioniert meist nichts Anderes mehr, so dass die Information (bzw. die Tests) nicht besonders hilfreich ist (sind).

    Gruß,

    Simon2.

    andeuten wollen: Moderne Betriebssysteme geben oftmals überhaupt nicht mehr "Kein Speicher" zurück, sondern kratzen alles zusammen oder arbeiten mit "optimistic alloc" (meine Wortschöpfung - bedeutet: OS sagt zu und hofft, dass bei der ersten Nutzung genug da ist) oder "lazy alloc" (beantwortet erstmal die Reservierung positiv, stellt aber tatsächlich erst mein ersten Zugriff bereit oder .....
    Da kann auch die beste Laufzeitumgebung oder das beste Programm nichts dran machen...

    Ich habe jetzt gerade keine Zeit/Lust, die länglichen Ausführungen Sutters dazu hier zu wiederholen - Fazit: Besser nicht damit rechnen.

    Gruß,

    Simon2.



  • Wenn das Programm den ganzen PC niederreisst, dann ist wohl entweder das OS oder der Compiler (bzw. die std.lib.) ... nicht sehr "stabil".

    Was du machst "darf" allerdings dazu führen dass das Programm mit terminate() beendet wird, da das "cout << ..." ja wieder Speicher brauchen könnte... Und wo soll der herkommen, wenn du alles weggelutscht hast. Fliegt also nochmal eine bad_alloc, da die fängt keiner, terminate(), aus.

    Auf Windows geht's übrigens, wenn es auch EWIG dauert bis alle listnodes wieder freigegeben wurden (die Windows Heap Implementierung wird im Fall "Speicher komplett zugemüllt" anscheinend etwas sehr langsam).



  • Simon2 schrieb:

    mase schrieb:

    Ich wollte das ganze mal mit einem kleinen Programm testen:
    ...
    Keine bad_alloc Ausnahme.
    ...

    🙄 *seufz*
    Genau das habe ich bereits mit

    Simon2 schrieb:

    ...
    ... aber ich würde mich nicht allzu sehr darauf versteifen. Nach Herb Sutter, funktioniert mit modernen Betriebssystemen oftmals das "bad_alloc" nicht so, wie man das denkt - und wenn man dann doch ein bad_alloc bekommt, funktioniert meist nichts Anderes mehr, so dass die Information (bzw. die Tests) nicht besonders hilfreich ist (sind).

    Gruß,

    Simon2.

    andeuten wollen: Moderne Betriebssysteme geben oftmals überhaupt nicht mehr "Kein Speicher" zurück, sondern kratzen alles zusammen oder arbeiten mit "optimistic alloc" (meine Wortschöpfung - bedeutet: OS sagt zu und hofft, dass bei der ersten Nutzung genug da ist) oder "lazy alloc" (beantwortet erstmal die Reservierung positiv, stellt aber tatsächlich erst mein ersten Zugriff bereit oder .....
    Da kann auch die beste Laufzeitumgebung oder das beste Programm nichts dran machen...

    Ich habe jetzt gerade keine Zeit/Lust, die länglichen Ausführungen Sutters dazu hier zu wiederholen - Fazit: Besser nicht damit rechnen.

    Gruß,

    Simon2.

    Nnnnnnnnnnnjaaain. Ganz so kann man das auch nicht sagen. Klar wird Speicher nicht sofort committed, aber (wenn wir mal vom Usermode ausgehen): den Adressraum kann man ganz einfach reservieren, und bei 32 Bit Systemen ist es eher der der ausgeht, nicht der Speicher. Allerdings: WENN so ein Problem auftritt, dann schiesst das OS den Prozess sowieso ab bzw. regelt das über irgendwelche OS spezifischen Methoden.

    Auf jeden Fall wird man immer nen Pointer ODER nen bad_alloc von new bekommen. Wenn bei der Verwendung des Speichers dann was knallt... ok, das hat mit C++ dann nixmehr zu tun.

    Und einfach zu sagen "bad_alloc geht eh nicht gescheit, also tun wir so als ob es das nicht gäbe" wäre der GANZ falsche Schluss.

    C++ sagt: "kein Speicher" wird mit bad_alloc beantwortet, und genau danach sollte sich der C++ Programmierer richten. Die Implementierung hat dafür zu sorgen dass es auch wie versprochen funktioniert. Wenn sie es nicht tut ist sie eben schlecht 🙂

    p.S.: hast du nen Link zu dem Paper von Sutter? Würde mich interessieren warum der jetzt meint das wäre ein Problem... ich sehe dieses Problem nämlich nicht 😉



  • Es hat genau genau einmal funktioniert, als ich die ganze Schleife in
    try eingeschlossen hab.
    Weitere Versuche scheiterten. Das Programm wurde terminiert.



  • hustbaer schrieb:

    ...
    Und einfach zu sagen "bad_alloc geht eh nicht gescheit, also tun wir so als ob es das nicht gäbe" wäre der GANZ falsche Schluss....

    Den wollte ich auch nicht ziehen, sondern nur, dass sich evtl. diese wilde Experimentiererei gar nicht lohnt.
    Ich würde auch sagen: Ein Programm sollte auf ein bad_alloc vorbereitet sein .... aber zu versuchen, genau die Rahmenbedingungen herauszufinden, in denen diese fliegt, wäre wohl vergebliche Liebesmüh.

    Gruß,

    Simon2.

    P.S.: Link von Sutter hab' ich leider gerade nicht.



  • Aber jetzt mal unabhängig von bad_alloc.
    Wenn das try wie hier in einer Schleife steht, das catch aber ausserhalb
    der Schleife. Kann das so funktionieren, wenn eine exception geworfen wird?
    Oder muss ich unbedingt die Schleife in den try Block einschliessen?
    Ich hatte es ja hier so vor, dass der catch Block die Schleife unterbricht.



  • Prinzipell, sollte es so auch funktionieren. Du verlässt ja danach die Schleife mit break. Aber wie hustbar schon sagte:
    Das cout << .. kann natürlich auch intern Speicher reservieren und wieder ein bad_alloc werfen.

    DJohn



  • @mase: ja, so wie das try-catch von dir da steht geht das schon ok grundsätzlich.
    Wenn du die bad_alloc Sache ausprobieren willst probiers mal so:

    #include <iostream> 
    #include <list> 
    #include <new> 
    #include <memory>
    
    using namespace std; 
    
    struct foo
    {
    	foo() {}
    	char x[10*1024*1024];
    
    	// speeds up things considerably
    	// (memory does not have to be touched,
    	//  so it's only reserved but never really committed)
    	foo(foo const&) {}
    	foo& operator = (foo const&) {}
    };
    
    int main() 
    {
    	size_t i = 0;
    	try
    	{
    		list<foo> l; 
    		auto_ptr<foo> f(new foo()); // foo is too big for the stack
    		for (;;++i)
    			l.push_back(*f);
    	}
    	catch (exception const& e)
    	{
    		cout << "exception: " << typeid(e).name() << endl;
    		cout << "what: " << e.what() << endl;
    		cout << "i = " << i << endl;
    	}
    
    	cin.get();
    	return 0;
    }
    

    Das läuft bei mir in Bruchteilen von einer Sekunde, und liefert brav den erwarteten bad_alloc. Terminiert danach auch ganz brav mit exit code 0, also kein Problem, alles wie es sein sollte.
    Der copy-ctor von foo ist übrigens der Grund warum das so schnell läuft, so wird nämlich das Array "x" in den "foo" drinnen nie initialisiert. Dadurch wird der Speicher zwar angefordert, aber eben nie committed, was natürlich wahnsinnig viel schneller geht. Vor allem wenn man weniger Speicher im Rechner hat als dieser adressieren kann.



  • Dann wird es wohl auch nie möglich sein, vernünftig auf ein bad_alloc
    zu reagieren.
    In dem Programm, an dem ich gerade bin, füge ich Objekte einer Liste zu.
    Im Falle, dass kein Speicher mehr verfügbar ist, wollte ich dem Benutzer
    die Möglichkeit geben, alles in eine Datei zu schreiben, oder das Programm
    das ganze von selbst machen lassen, ehe es terminiert wird.
    Oder könnte man theoretisch beim Programmstart etwas unnötigen Speicher
    reservieren lassen, etwa ein char-Array mit ein paar tausend Feldern?
    Wenn ein bad_alloc aufgefangen wird, könnte man mit delete diesen Speicher
    wieder freigeben, und hätte noch etwas Speicher, eine Dateiaktion
    durchzuführen.
    Ich weiss, das ist jetzt etwas weit hergeholt, aber sonst fällt mir nichts
    ein.
    Ich will das Programm nur etwas robuster machen, und selbst bei den heutigen
    RAM-Grössen sollte man das doch in Betracht ziehen.
    Oder sollte ich doch ganz drauf verzichten?



  • Wenn du dein Programm robust machen willst, dann guck einfach zu dass du für das was du im "bad_alloc" Fall machen willst keinen Speicher brauchst.
    Mit C++ streams wird das u.u. schwer gehen (AFAIK gibt's da kaum Garantien die die Standard C++ Lib erfüllen bzw. geben muss), aber wenn du auf OS API Ebene runtergehst ist das relativ einfach. Du musst z.B. nur die Datei immer offen haben, evtl. noch einen kleinen Puffer zum Schreiben, fertig. Deine Speicherroutinen dürfen natürlich auch nicht auf dynamischen Speicher angewiesen sein, aber das ist meist relativ einfach zu realisieren.
    Doof sind natürlich Klassen die selbst intern Speicher anfordern wie std::string oder auch alle Container, aber man kann ja auch ohne diese arbeiten.

    Die Frage ist eher ob es sich auszhalt, oder ob es nicht reicht im Fall der Fälle einfach nur dafür zu sorgen dass die aktuelle Operation abgebrochen wird und das Programm wenn möglich weiterläuft oder sonst eben sauber terminiert.


Log in to reply