malloc und lebensdauer



  • Hallo Kollegen,
    ich habe hier ein kleines Problem mit einer C-Function.

    Bevor ich zum Problem komme:
    Ich darf die Funktion nicht anpassen, da diese nicht von uns stammt. Die Firma die uns diese zur Verfügung stellt ist zur Zeit nicht verfübgar 😞 da sie leider in einem Schwebeverfahren hängt (Insolvenz, Schulden, etc....).

    Nun zum Coding Problem:

    Die Funktion allokiert speicher dynmaisch mittels malloc, gibt diesen aber in der Funktion nicht mehr frei.

    Nach meinen Verständniss heisst das doch, auch wenn ich die Funktion verlasse bleibt der Speicher noch so lange allokiert, bis das komplette Programm terminiert/beendet wird. Ist das so richtig?

    Falls ich mich nicht irre, heisst das aber das wir in einem Speicherengpass laufen, da der Speicher ja nie mehr freigegeben wird.

    Gibt es eine Möglichkeit den Pointer auf den allokierten Speicher ausserhalb der Funktion noch irgendwie zu bekommen und dann einen free mit hilfe des pointers auslösen?

    Bitte bedenkt das wir den Code der fremden Firma nicht anpassen dürfen, deswegen muss es außerhalb der Funktion geschehen 😞

    Ich weiss leider nicht mehr weiter...

    Da die Firma auch Funktionen mit Threads hat, hier die Fragen:

    1. Speicher der dynamisch in einer Funktion allokiert wurde (z.B. mittels malloc) bleibt doch auch nach verlassen einer Funktion allokiert, wenn kein free erfolgt ist - richtig?
    2. Speicher der dynamisch in einem Thread allokiert wurde, bleibt auch allokiert wenn der Thread beendet ist - richtig?
    3. Gibt es eine Möglichkeit wie ich Speicher außerhalb von Threads / Funktionen wieder freigeben kann, wenn der Pointer nur innerhalb der Funktion / Thread bekannt war?

    Ihr würdet mir sehr helfen 🙂



  • 1. ja
    2. ja
    3. nein, wenn nur auf üblen Umwegen



  • codefrag schrieb:

    Bitte bedenkt das wir den Code der fremden Firma nicht anpassen dürfen, deswegen muss es außerhalb der Funktion geschehen 😞

    bist du sicher, dass die funktion 'malloc' macht und den pointer nicht zurückgibt, free'd o.ä.? wenn ja: sowas ist ein mieser bug d.h. ich würde dann trotzdem die funktion ändern.



  • net schrieb:

    codefrag schrieb:

    Bitte bedenkt das wir den Code der fremden Firma nicht anpassen dürfen, deswegen muss es außerhalb der Funktion geschehen 😞

    bist du sicher, dass die funktion 'malloc' macht und den pointer nicht zurückgibt, free'd o.ä.? wenn ja: sowas ist ein mieser bug d.h. ich würde dann trotzdem die funktion ändern.

    hallo,
    ja da bin ich mir ganz sicher...
    Es wird Speicher mittels malloc für verkette listen allokiert.

    Aber der Speicher wird definitiv nicht mehr mit free freigeben 😉 da es keine free anweisung gibt... und in der Funktion auch keine weitere Funktionen oder sonst was aufgerufen wird, was dann intern wieder den Speicher freigeben könnte... Als Rückgabewert gibt es einen char pointer auf einen Eintrag aus einer Liste...

    Es ist definitiv ein mieser Bug 😡 den ich eben gerne mit einer Möglichkeit von extern beheben würde ohne in die Funktion einzugreifen.

    Ich darf den Code auf Grund von Copyrights,etc. nicht ändern, da wir eben noch mit der Firma gerichtlich zu Werk sind 😞
    Mein Chef hat es mir definitiv verboten...

    Also keine Chance.... verdammt ich habs mir schon gedacht 😡 😡 😡



  • codefrag schrieb:

    Als Rückgabewert gibt es einen char pointer auf einen Eintrag aus einer Liste...

    vielleicht kannste dich durch die liste hangeln und selber alles freigeben?



  • [quote="net"]

    codefrag schrieb:

    Als Rückgabewert gibt es einen char pointer auf einen Eintrag aus einer Liste...

    hmm das ist ein interessanter Ansatzpunkt.

    Ich müsste mir mittels memcpy den Inhalt woanders hinkopieren.

    Aber wie kommt man an die Anfangsadresse der verkettteten Liste?

    Ich kann von dem Pointer immer wieder ein sizeof(char) abziehen, so das ich in das vorherige Feld springe, bzw. die Memoryadresse von diesen bekomme.

    Aber wie merke ich das ich jetzt am Anfang der Liste bin und es bei der nächsten Subtraktion nicht mehr weiter geht ...

    btw. oh gott ist das ein gefrickel 😉 😉



  • poste doch einfach mal den code der funktion hier hin....



  • ... ich hätte da mal 'ne ganz andere Idee, basierend auf Linux/Unix (mag vielleicht bei Windows auch irgendwie möglich sein).

    (1) Wenn Ihr im Besitz des Codes seid, könnt Ihr den Code auch übersetzen und linken?
    => Dann würde sich eine Umdefinition/Kapselung der malloc/free-Aufrufe anbieten:
    #define malloc(n) my_malloc(n)
    und die Definition von my_malloc mit einer eigenen Verwaltungsstruktur (mit eigener Verkettung), die es ermöglicht, nach Verlassen der aufgerufenen fremden Funktion alles wieder freizugeben. Hierzu reicht eine Modifikation des Makefiles oder eines Include-Files. Das Ganze ist sicherlich auch thread-spezifisch möglich, wenn man eine Verkettung pro Thread vorsieht.

    (2) Wenn Ihr den Code nicht selbst übersetzt:
    => dann besteht evtl. noch die Möglichkeit, mit einer veränderten Linker-Reihenfolge die malloc/free-Routinen durch eigene Funktionen zu überlagern. Doch das wird etwas komplizierter. Hierzu dann auch noch die Frage: statisches oder dynamisches Linken (bei Linux/Unix)?

    Jetzt wäre es durchaus interessant zu wissen, welchen der beiden obigen Ansätze man verfolgen könnte ...und: Unix oder Windows?



  • codefrag schrieb:

    net schrieb:

    codefrag schrieb:

    Bitte bedenkt das wir den Code der fremden Firma nicht anpassen dürfen, deswegen muss es außerhalb der Funktion geschehen 😞

    bist du sicher, dass die funktion 'malloc' macht und den pointer nicht zurückgibt, free'd o.ä.? wenn ja: sowas ist ein mieser bug d.h. ich würde dann trotzdem die funktion ändern.

    hallo,
    ja da bin ich mir ganz sicher...
    Es wird Speicher mittels malloc für verkette listen allokiert.

    Aber der Speicher wird definitiv nicht mehr mit free freigeben 😉 da es keine free anweisung gibt... und in der Funktion auch keine weitere Funktionen oder sonst was aufgerufen wird, was dann intern wieder den Speicher freigeben könnte... Als Rückgabewert gibt es einen char pointer auf einen Eintrag aus einer Liste...

    Eventuell wird das was, wenn Du zuordnen kannst, was Du da eigentlich zurückbekommst.
    Du sagst, Du bekommst ein (char *)... wenn Du weißt, wie die Liste aufgebaut ist, z.B.

    struct Entry
    {
      struct Entry * Next, * Prev;
      struct List  * Owner;
    
      char         * Data;  // <- das ist Dein Rückgabewert;
    };
    

    Dann kannst Du das ja auch wieder zurückrechnen, so dass Du an Owner kommst.
    Wenn es keine Chance gibt, an 'struct List *' zu kommen... Problem...

    Lass mich raten... Problem...?

    Okay, nächster Ansatz ^^ diesmal richtig böse, nicht portabel, gegen alle guten Sitten...

    Du rufst die Funktion nicht mehr direkt auf, sondern schreibst eine Funktion, die die fehlerhafte Funktion ruft, speicherst das Ergebnis in einer
    lokalen Variable ab. Solange Du nur Deine lokalen Variablen benutzt, ist der Stack-Bereich vor Deiner Funktion unverändert.
    Du darfst offiziell nicht davon ausgehen, dass die Daten auf dem Stack verbleiben würden. Das Löschen von Daten auf dem Stack wäre aber nicht performant und daher macht's vermutlich kein Compiler, auch die Verwendung von Listen ist alles andere als schnell, wenn man nur eine Funktion aufruft. Man kann somit also quasi ein bißchen davon ausgehen, dass auch Dein Compiler mit einem Stack arbeitet und diesen bei verlassen der Funktion nicht reinigt...

    Du kannst Dich also nun im Stack zurückhangeln und hast somit Zugriff auf die lokalen Variablen der Funktion, die Du zuvor aufgerufen hast, zu dem Zeitpunkt, als sie die Funktion verlassen hat.
    Sollte der nicht freigegebene Zeiger sich also nach dem Verlassen der fehlerhaften Funktion noch in einer lokalen Variablen der zuvor aufgerufenen Funktion befinden, so kannst Du ihn so doch noch erreichen.

    Das garantiert Dir kein Standard, das ist auch kein Work-Arround, es ist davon auszugehen, dass es nicht portabel ist, selbst das Aufspielen einer anderen Compilerversion könnte diesen Hack kaputt machen. Das ist Hacking und sollte keine dauerhafte Lösung darstellen... kann aber funktinieren, wenn die Adresse noch irgendwo vorhanden ist. 😉
    Und wenn's funktioniert, bis die Firma pleite ist und ihr deren Code endlich debuggen könnt, passt's ja :->

    Lass mich raten, die sind wegen fehlerhafter Software pleite gegangen?



  • das was jox geschrieben hat ist wohl das beste. irgendwie malloc überladen so dass man alle mallocs mitloggen kann...



  • Halllo Jungs,
    erstmal muss ich ein Lob für dieses Forum aussprechen.
    So professionelle und kompetente Hilfe wie hier habe ich noch in keinen anderen Forum gesehen bzw. erlebt. Also erstmal vielen Dank für euere Tipps... 👍 👍

    und nun zum Problem...

    jox schrieb:

    ... ich hätte da mal 'ne ganz andere Idee, basierend auf Linux/Unix
    (mag vielleicht bei Windows auch irgendwie möglich sein).

    (1) Wenn Ihr im Besitz des Codes seid, könnt Ihr den Code auch übersetzen und linken?
    => Dann würde sich eine Umdefinition/Kapselung der malloc/free-Aufrufe anbieten:
    #define malloc(n) my_malloc(n)
    und die Definition von my_malloc mit einer eigenen Verwaltungsstruktur (mit eigener Verkettung), die es ermöglicht, nach Verlassen der aufgerufenen fremden Funktion alles wieder freizugeben. Hierzu reicht eine Modifikation des Makefiles oder eines Include-Files. Das Ganze ist sicherlich auch thread-spezifisch möglich, wenn man eine Verkettung pro Thread vorsieht.

    Das Anwendung ist eine Client-Server Applikation basierend auf Sockets. Der Client läuft unter Windows (MFC) und der Server auf Linux. Das Problem-Funktion wird nur im Serverteil benutzt, da diese die verschiedenen Anfragen der Clients untereinander handelt...

    @ Jox: Du bist mein Held. Ich könnte dich knuddeln 🤡 🤡 Habe zwar etwas gebraucht und ein paar graue Haare sind es auch mehr geworden aber es funktioniert!! Das Serverprogramm läuft nun nicht mehr in einen Memory Leak.
    Vielen vielen Dank für den Tipp ...

    @ Xin:
    Den bösen Ansatz finde ich richtig spannend 😋 das muss ich mal ausprobieren wenn ich viel/mehr Zeit habe 😉 und nicht unsere Kunden im Nacken sitzen 😃
    Nur mal zur Info für mich, denn ich dachte bisher:
    Lokale Variablen und Literale werden auf dem Stack abgelegt, allokiere ich allerdings Speicher mittels malloc so wird dieser auf dem Heap reserviert? Der Pointer der auf den Bereich im Heap zeigt ist natürlich wieder eine lokale "variable". Aber da ich die Funktion aus meiner Funktion aufrufe, bleiben doch nur die lokalen variablen der externen Funktionen auf den Stack. D.h. ich müsste mir den Pointer der auf den allokierten Speicherbereich (Anfangsadresse) suchen und diesen dann freigeben...
    Ich stelle mir das aber sehr sehr sehr kompliziert vor....

    Und wenn's funktioniert, bis die Firma pleite ist und ihr deren Code endlich debuggen könnt, passt's ja :->
    Lass mich raten, die sind wegen fehlerhafter Software pleite gegangen?

    Die Firma erzeugte Softwaremodule für Client/Server Bereiche. Bis vor ca. 2 Jahren hatte die Firma auch noch richtig gute Entwickler, die ihr Handwerk verstanden haben. Doch dann kam der "große" Umschwung mit Personalabwanderungen auf Grund von internen Problemen (Gründe kenne ich genauer möchte ich aber nicht detailierter ausführen - stehe mit den Ex-Entwicklern noch in sehr guten Kontakt). Die Firma stellte frische Studis ein um die Software weiterzuentwickeln und zu warten... bis vor ca. einen halben Jahr waren nur noch 2 feste Entwickler über (die auf Grund ihre Alters nicht mehr wechselten).
    Als es dann bei einen Kunden von denen ordentlich knallte und auch "Schaden" im Sinne von Geldausfällen beim Kunden vorkam - klagten diese natürlich, etc..

    Uns hat es "noch" nicht so hart getroffen, da wir die Probleme noch immer rechtzeitig beheben konnten und unseren Kunden Work Arounds oder Fixes gebaut haben....

    Naja ich bin ma gespannt wie es hier weitergeht 😛



  • Das ist ja erfreulich, wenn Du mit meinen kurzen Tipps zum Erfolg gekommen bist 😉

    Doch zu Deinen Fragen:

    codefrag schrieb:

    Lokale Variablen und Literale werden auf dem Stack abgelegt, allokiere ich allerdings Speicher mittels malloc so wird dieser auf dem Heap reserviert?

    Ja, alle lokalen Variablen und aktuellen Parameter liegen auf dem Stack, malloc's sind im Heap (der i.d.R. nicht zusammenhängend sein muss!)

    codefrag schrieb:

    Der Pointer der auf den Bereich im Heap zeigt ist natürlich wieder eine lokale "variable".

    Ja.

    codefrag schrieb:

    Aber da ich die Funktion aus meiner Funktion aufrufe, bleiben doch nur die lokalen variablen der externen Funktionen auf den Stack.

    Ja.

    codefrag schrieb:

    D.h. ich müsste mir den Pointer der auf den allokierten Speicherbereich (Anfangsadresse) suchen und diesen dann freigeben...
    Ich stelle mir das aber sehr sehr sehr kompliziert vor....

    Ja, schätze ich auch so ein. Mit Verlassen der externen Funktion ist auch nicht sichergestellt, dass alle lokalen Variablen/Parameter von der externen Funktion im Stack noch verfügbar sind. Mit Wahrscheinlichkeit ist das zwar so, dass noch nicht alles übermangelt wurde, aber spätestens, wenn Du weitere Hilfsfunktionen aufrufst, wird der von der externen Funktion benutzte Stackbereich neu überschrieben. Ich bezweifel auch, dass der Stack zwangsläufig immer komplett dem User-Prozess erhalten bleibt. Theoretisch könnte bei einem Prozess/Task-Wechsel auch nicht mehr benötigte Memory-Pages von anderen Prozessen benutzt werden, das gilt natürlich auch für Stack-Pages (ist natürlich stark betriebssysemabhängig). Der Zeitpunkt ist dann unberechenbar und kann natürlich auch dann zuschlagen, wenn man nachträglich (als Work-around) einen alten Stack-Inhalt auswerten will. Also als Dauerlösung für einen produktiven Betrieb würde ich die Finger von solch einer Lösung lassen.

    Außerdem gibt es auch noch asynchrone Interrupts, die sich auf den User-Stack auswirken (z.B. setjmp()). Dann ist der alte Stackinhalt ggf. auch weg...



  • codefrag schrieb:

    @ Xin:
    Den bösen Ansatz finde ich richtig spannend 😋 das muss ich mal ausprobieren wenn ich viel/mehr Zeit habe 😉 und nicht unsere Kunden im Nacken sitzen 😃
    Nur mal zur Info für mich, denn ich dachte bisher:
    Lokale Variablen und Literale werden auf dem Stack abgelegt, allokiere ich allerdings Speicher mittels malloc so wird dieser auf dem Heap reserviert? Der Pointer der auf den Bereich im Heap zeigt ist natürlich wieder eine lokale "variable".

    Soweit alles richtig.

    codefrag schrieb:

    Aber da ich die Funktion aus meiner Funktion aufrufe, bleiben doch nur die lokalen variablen der externen Funktionen auf den Stack. D.h. ich müsste mir den Pointer der auf den allokierten Speicherbereich (Anfangsadresse) suchen und diesen dann freigeben...
    Ich stelle mir das aber sehr sehr sehr kompliziert vor....

    Einfach wär ja langweilig 😉 Kompliziert ist alles, was man noch nicht ausprobiert hat.
    Aber was sollte daran kompliziert sein?

    Du kennst die Parameter der Funktion, die Du suchst. Also kannst Du auch den Punkt bestimmen, wo die Variable, die Dich interessiert ungefähr sitzt. Mit zwei, drei einfachen Testprogrammen, kannst Du das Offset sogar exakt bestimmen.
    Da Du außerdem den Code besitzt und ihn nur nicht verändert kompiliert weitergeben darfst, kannst Du ja auch mal testweise die Funktion so kompilieren, dass in der gesuchten Variable ein besonderer Wert steht. 0x0badc0de ist hier mein Liebling. ^^

    Nun nimmst Du Deine Funktion und schnappst Dir die Adresse einer lokalen Variablen. Die liegt bekanntlich auch auf dem Stack, also weißt Du mit der Adresse, wo der Stack liegt. Nun hangelst Du Dich den Stack zurück bzw. vor und suchst 0x0badcode.
    Hättest Du den Code nicht gehabt, wäre ein guter Disassembler sicherlich hilfreich, so dass man statt malloc vielleicht testweise mal etwas anderes rufen könnte, was malloc ruft und zusätzlich dir die Adresse in eine statische Variable legt, nach der Du auf dem Stack suchen musst.
    Ich vermute, so ähnlich hast Du es mit #define MyMalloc() nun auch gelöst?

    Hast Du die Adresse und den Abstand zu Deiner lokalen Variablen auf dem Stack brauchst Du nur noch etwas wie
    free( ((char)&LokaleVariable) - Abstand) );
    aufzurufen.

    Aber die Möglichkeit, die jox vorgeschlagen hat, ist bei vorhandenem Sourcecode definitiv vorzuziehen.

    Viel Spaß beim rumprobieren!

    jox schrieb:

    Mit Verlassen der externen Funktion ist auch nicht sichergestellt, dass alle lokalen Variablen/Parameter von der externen Funktion im Stack noch verfügbar sind. Mit Wahrscheinlichkeit ist das zwar so, dass noch nicht alles übermangelt wurde, aber spätestens, wenn Du weitere Hilfsfunktionen aufrufst, wird der von der externen Funktion benutzte Stackbereich neu überschrieben.

    Stimmt... darum schrieb ich ja auch, dass er andere Funktionsaufrufe bitteschön zu lassen hat. ^^
    Ansonsten müsste er den relevanten Stackbereich erstmal kopieren.
    Auch dass nicht sicher ist, dass die Daten im Stack nicht mehr verfügbar sind, schrieb ich. Werden aber keine anderen Funktionen gerufen, die den Stack überschreiben und räumt auch der Compiler nicht auf, so ist das Auslesen des Stacks eine legale Operation, da ich einen Speicherbereich auslese, der meinem Programm zur Verfügung gestellt wurde. Dass das nicht im Sinne der Sprache C oder C++ ist, ist klar... es stellt ja auch nicht die alltägliche Programmierung dar.

    jow schrieb:

    Ich bezweifel auch, dass der Stack zwangsläufig immer komplett dem User-Prozess erhalten bleibt. Theoretisch könnte bei einem Prozess/Task-Wechsel auch nicht mehr benötigte Memory-Pages von anderen Prozessen benutzt werden, das gilt natürlich auch für Stack-Pages (ist natürlich stark betriebssysemabhängig).

    Abgesehen davon dass es sehr unwahrscheinlich ist, dass diese durchgehend verwendeten Pages dieser Bereich ausgelagert würden, würden sie dann halt wieder eingeladen - und zwar inkl. der Daten, die die zuvor gerufene hinterlassen hat.

    codefrag schrieb:

    Also als Dauerlösung für einen produktiven Betrieb würde ich die Finger von solch einer Lösung lassen.

    Auch dem stimme ich nicht nur zu - ich schrieb' die Warnung ebenfalls ^^

    Solange er den Compiler nicht wechselt und der verwendete Compiler keine nenneswerten Änderungen mit einer neuen Version macht, ist diese Arbeitsweise sicherlich zurecht in Verruf, aber dennoch safe. Man hämmert ja auch keine Nägel mit der Rohrzange rein - trotzdem funktioniert's, wenn man jemanden hat, der mit der Rohrzange richtig umgehen kann.



  • Ich entschuldige mich hier offiziell, falls ich irgendwelche Anmerkungen von Xin überlesen oder nicht richtig interpretiert haben sollte (oder einfach nochmals mit meinen Worten wiederholt haben sollte).

    Noch einige Anmerkungen:

    Xin schrieb:

    Das Löschen von Daten auf dem Stack wäre aber nicht performant und daher macht's vermutlich kein Compiler,...

    Dem ist so, so einen Code macht definitiv kein Compiler. Der Stack wird von Funktionsaufruf zu Funktionsaufruf beim Aufräumen nur dadurch bereinigt, indem der sogenannte Framepointer versetzt wird.

    Xin schrieb:

    Xin:
    Abgesehen davon dass es sehr unwahrscheinlich ist, dass diese durchgehend verwendeten Pages dieser Bereich ausgelagert würden, würden sie dann halt wieder eingeladen - und zwar inkl. der Daten, die die zuvor gerufene hinterlassen hat.

    Dem ist leider halt nicht so, nicht benötigte Memory-Pages werden von dem Prozess wieder freigegeben und ggf. anderen Prozessen zugeordnet. Die alten Daten werden nicht mehr eingelagert und sind somit nicht mehr verfügbar (soweit die Theorie vom Memory-Management; ist zwar - wie gesagt - eine unwahrscheinliche Situation, nämlich dass der Aufruf dieser externen Funktion Stackbereiche an der Grenze zu einer neuen Memory-Page belegt; tritt nach Murphy irgendwann auf, spätestens bei Last ;-))

    ... und wie gesagt, durch asynchrone Interrupts kann der User-Stack zu beliebigen Zeiten an der dann aktuellen Stelle (sprich Stackpointer) beginnend neu überschrieben werden. Auch in diesem Fall erhält man dann vermurkste Referenzen, die man als Listenpointer interpretiert oder mit denen man ein free() durchführen will. Ich schätze mal, dass das dem Prozess nicht gut bekommt.

    Die von Xin vorgeschlagene Methode mag vielleicht auch funktionieren, ich befürchte aber, dass sie (insbesondere bei hoher Systemlast) halt zu schwer reproduzierbaren Fehlern führen kann.



  • jox schrieb:

    Ich entschuldige mich hier offiziell, falls ich irgendwelche Anmerkungen von Xin überlesen oder nicht richtig interpretiert haben sollte (oder einfach nochmals mit meinen Worten wiederholt haben sollte).

    So offiziell ist das nicht notwendig, aber mich störte schon, dass sich das ganze wie eine Korrektur meiner gleichlautenden Aussagen las.
    Gegen eine Kritik an dieser Methode habe ich nichts einzuwenden, da es sich ja auch definitiv um einen zu kritisierenden Programmierstil handelt.

    jox schrieb:

    Xin schrieb:

    Xin:
    Abgesehen davon dass es sehr unwahrscheinlich ist, dass diese durchgehend verwendeten Pages dieser Bereich ausgelagert würden, würden sie dann halt wieder eingeladen - und zwar inkl. der Daten, die die zuvor gerufene hinterlassen hat.

    Dem ist leider halt nicht so, nicht benötigte Memory-Pages werden von dem Prozess wieder freigegeben und ggf. anderen Prozessen zugeordnet. Die alten Daten werden nicht mehr eingelagert und sind somit nicht mehr verfügbar (soweit die Theorie vom Memory-Management; ist zwar - wie gesagt - eine unwahrscheinliche Situation, nämlich dass der Aufruf dieser externen Funktion Stackbereiche an der Grenze zu einer neuen Memory-Page belegt; tritt nach Murphy irgendwann auf, spätestens bei Last ;-))

    Der Compiler gibt den Stack aber nicht frei, da der Stack ja weiterhin verwendet wird. Nehmen wir an, wir haben einen Stack von 8kB und Pages von 4kB, dann könnte sich das Szenario an der Grenze von 4kB im Stack bewegen. Da der Stack aber ein Block von 8kB Größe ist, der nicht freigegeben wurde, kann das Memory-Management nicht von sich aus entscheiden, dass es die eine Hälfte jetzt mal falsch wieder aus dem Swap-Space zieht. Dass es sich um einen Funktionsstack handelt wird bei malloc üblicherweise nicht mitangegeben und ob der Stack grade an der Stelle benutzt wird, die ausgelagert wird, wird das MemoryManagement wohl auch kaum prüfen.

    Schlussfolgerung: Der fragliche Segment wird ohne Veränderung wieder aus dem Swap-Space gezogen, niemals einfach nur freigegeben und die Tatsache, dass mein Zeiger eine Adresse ausgerechnet hat, zwischenzeitlich aber das Stack-Segment durch Swapping sich an eine andere Stelle des physikalischen Speichers verschoben, wird durch die MMU wieder grade gebogen.

    jox schrieb:

    Die von Xin vorgeschlagene Methode mag vielleicht auch funktionieren, ich befürchte aber, dass sie (insbesondere bei hoher Systemlast) halt zu schwer reproduzierbaren Fehlern führen kann.

    Alles Probleme, um die ich mich als Programmierer zur Ausführungszeit meines Programms nicht zu kümmern brauche, da ansonsten Swapping nicht funktionieren würde.

    Ich habe nie gesagt, dass die Methode Ausdruck von Stil und Eleganz beim Programmieren darstellen soll - im Gegenteil.
    Aber sie funktioniert, solange der Compiler beim Funktionsaufruf Argumente in gleicher Reihenfolge auf den Stack schreibt und die gesuchte Information als lokale Variable vorliegt.



  • (1)

    Xin schrieb:

    Der Compiler gibt den Stack aber nicht frei, da der Stack ja weiterhin verwendet wird. Nehmen wir an, wir haben einen Stack von 8kB und Pages von 4kB, dann könnte sich das Szenario an der Grenze von 4kB im Stack bewegen. Da der Stack aber ein Block von 8kB Größe ist, der nicht freigegeben wurde, kann das Memory-Management nicht von sich aus entscheiden, dass es die eine Hälfte jetzt mal falsch wieder aus dem Swap-Space zieht. Dass es sich um einen Funktionsstack handelt wird bei malloc üblicherweise nicht mitangegeben und ob der Stack grade an der Stelle benutzt wird, die ausgelagert wird, wird das MemoryManagement wohl auch kaum prüfen.

    Die Verwaltung des Stacks liegt nicht beim Compiler, sondern beim Betriebssystem. Die Größe des Stacks wird vom Betriebssystem bei Bedarf dynamisch angepasst (in beide Richtungen, d.h. in Deinem Beispiel könnte der Stack wieder auf 4kB reduziert werden).
    Was ich meine, ist nicht, dass beim Swappen ein Problem entsteht, sondern dass nicht mehr benötigte Memory-Pages dem Prozess evtl. auch nicht mehr zur Verfügung stehen. Ich muss daher meine ursprüngliche Aussage noch erweitern: Es stehen evtl. nicht nur andere Inhalte in einer freigegebenen Memory-Page, sondern darüber hinaus kann der ursprüngliche Prozess gar nicht mehr auf die Memory-Page zugreifen, weil sie ihm nicht mehr gehört (es gibt dann einen Memory-Zugriffsfehler).

    (2) ... und wie will man verhindern, dass durch asynchrone Interrupts der User-Stack wieder benutzt wird und die alten Daten dadurch überschrieben werden?

    Wie gesagt, die Methode mag zwar meistens funktionieren, sie ist aber nicht 100% sicher (dagegen sprechen sowohl (1) als auch (2)), und das gilt auch bei unveränderter Systemumgebung (Betriebssystem, Compiler) ... leider 😢



  • jox schrieb:

    Die Verwaltung des Stacks liegt nicht beim Compiler, sondern beim Betriebssystem. Die Größe des Stacks wird vom Betriebssystem bei Bedarf dynamisch angepasst (in beide Richtungen, d.h. in Deinem Beispiel könnte der Stack wieder auf 4kB reduziert werden).

    Der Punkt geht an Dich, konstante Stacks sind heutzutage nicht mehr State-of-the-Art.
    Auch wenn es quasi unmöglich ist, dass eine der Speicher so knapp verkleinert wird, dass die Daten der Funktion ebenfalls freigegeben werden. Trotzdem sehe ich die theoretische Möglichkeit, die Du ansprichst, und damit hast Du recht, dass meine Lösung nicht 100%ig sicher ist.

    jox schrieb:

    ... und wie will man verhindern, dass durch asynchrone Interrupts der User-Stack wieder benutzt wird und die alten Daten dadurch überschrieben werden?

    Grübel... hier muss ich erstmal fragen, ob die Interrupts nicht mit eigenem Stack vom OS vorbeischauen... schließlich landen wir hier in einer parallelen Verarbeitung, da reicht ein Stack nicht mehr.
    Da sie nämlich auch einschlagen können während der User-Stack grade in Bearbeitung ist, kann man den Stack nicht einfach mal so überschreiben.
    Aber das müsste ich nachschlagen und Du hast Deinen Beweis oben schon erbracht.



  • Xin schrieb:

    ... konstante Stacks sind heutzutage nicht mehr State-of-the-Art.

    Dynamische Anpassung der Stackgröße war bei Unix m.E. schon immer so (siehe auch das Standardwerk zum Uralt-Unix von Maurice Bach: Design of the UNIX Operating System von 1986)

    Xin schrieb:

    ... ob die Interrupts nicht mit eigenem Stack vom OS vorbeischauen...

    Bei den "normalen" Prozessoren (ungleich Mikroprozessoren) mit Multitasking-Unterstützung gibt es einen Kernel-Stack für Interrupts. Bei Unix gibt es aber auch die Möglichkeit, dass asynchron zum normalen Programmablauf Interrupts auf User-Ebene zuschlagen. Dies ist möglich über setjmp()/longjmp() oder auch über signals, wo dann jeweils der User-Kontext hergestellt wird und auch der User-Stack benutzt wird.

    Xin schrieb:

    Da sie (die Interrupts) nämlich auch einschlagen können während der User-Stack grade in Bearbeitung ist, kann man den Stack nicht einfach mal so überschreiben.

    Doch, das wird z.B. bei Mikroprozessoren (z.B. Z80 o.ä.) so gemacht: Auf einen Interrupt wird erst am Ende eines Befehls reagiert, d.h. laufende Stackoperationen (push, pop, call oder return) werden erst komplett ausgeführt, bevor der Interrupt abgearbeitet wird. Damit bleibt der Stack konsistent.

    ... für weitere Fragen (je HW-näher desto besser) bin ich auch gerne zu haben ...

    P.S.: Auf setjmp() hatte ich zwar bereits am 7.8. hingewiesen, aber so etwas kann man ja übersehen. Soll mir ja auch schom mal vorgekommen sein 😉



  • jox schrieb:

    Doch, das wird z.B. bei Mikroprozessoren (z.B. Z80 o.ä.) so gemacht: Auf einen Interrupt wird erst am Ende eines Befehls reagiert, d.h. laufende Stackoperationen (push, pop, call oder return) werden erst komplett ausgeführt, bevor der Interrupt abgearbeitet wird. Damit bleibt der Stack konsistent.

    die isr's benutzen auch selber den stack. sogar bei verschachtelten interrupts geht das...



  • jox schrieb:

    Dynamische Anpassung der Stackgröße war bei Unix m.E. schon immer so (siehe auch das Standardwerk zum Uralt-Unix von Maurice Bach: Design of the UNIX Operating System von 1986)

    Wir haben nicht alle das Glück unser ganzes Leben auf Unix-Derivaten verbringen zu dürfen. 8-(
    Abgesehen davon habe ich 1986 grade erst angefangen zu programmieren... PRINT "HALLO WELT"...
    Was Unix da für Funkionsstacks hatte oder ob Unix überhaupt existiert, war für mich da zweitrangig - da war ich noch stolz, dass ich mir '87 mein erstes Floppy-Laufwerk dazukaufen konnte... ^^

    jox schrieb:

    Xin schrieb:

    ... ob die Interrupts nicht mit eigenem Stack vom OS vorbeischauen...

    Bei den "normalen" Prozessoren (ungleich Mikroprozessoren) mit Multitasking-Unterstützung gibt es einen Kernel-Stack für Interrupts. Bei Unix gibt es aber auch die Möglichkeit, dass asynchron zum normalen Programmablauf Interrupts auf User-Ebene zuschlagen. Dies ist möglich über setjmp()/longjmp() oder auch über signals, wo dann jeweils der User-Kontext hergestellt wird und auch der User-Stack benutzt wird.

    Bzgl. setjmp und longjmpg() kann ich mich leicht rausreden, da die Frage iostreams enthielt, der Sourcecode damit im falschen Forum steht und man als C++ Programmierer derartige Probleme doch bitteschön via Exceptions zu lösen hat. ;-P

    jox schrieb:

    P.S.: Auf setjmp() hatte ich zwar bereits am 7.8. hingewiesen, aber so etwas kann man ja übersehen. Soll mir ja auch schom mal vorgekommen sein 😉

    *lach* Dann war das auch am 7.8. schon unpassend für ein C++-Programm. 😉


Log in to reply