Pointer wegen möglichen Stackoverflow?


  • Gesperrt

    @Finnegan sagte in Pointer wegen möglichen Stackoverflow?:

    Auch hier fällt mir auf Anhieb kein Problem ein, bei dem das bei auf ausgewachsenen PCs üblichen Stack-Größen zum Problem wird

    Dito. Für gewöhnlich wird dann eher die Laufzeit zum Problem.

    Wie groß ist denn ein "Element", und wie viele gibt es?



  • Erst mal vielen Dank für die vielen Rückmeldungen.

    @Schlangenmensch sagte in Pointer wegen möglichen Stackoverflow?:

    @KK27 In meinem Verständniss von "modernem" C++, möchte man Value Semantics haben, die STL bietet dafür jede Menge Helfer, wie std::variant, std::optional und so weiter. Das macht, meiner Meinung nach, Code einfacher zu lesen und zu verstehen.
    Alles in einen Smart Pointer zu packen, widerspricht dem Verständnis.

    std::variant und std::optional kann ich leider nicht verwenden. Wenn ich mich nicht täusche ist unser Compiler ein 98er Compiler mit experimentiellen Featues wie Smart Pointern. Nicht alles was in C++ 11 verfügbar ist, können wir verwenden. Aber vieles wie Smart Pointer. Wir wollten unseren Compiler mal updaten, aber da gibt es Probleme mit uralten Bibliotheken und natürlich mit der Zeit. Nur mal so am Rande erwähnt.

    Zum Beispiel habe ich Klasse(n) für ein Projekt geschrieben, die Daten aus einer SQL Datenbank laden. Daten die in der Datenbank den Wert NULL haben können, sind bei mir unique_ptr. Entweder haben sie einen Wert oder wenn sie NULL sind, dann ist es ein nullptr. Gerne hätte ich std::optional in diesem Kontext verwendet. Etwas besseres ist mir damals nicht eingefallen.

    In meinem ganzen Leben als Programmierer ist mir einmal ein Stackoverflow passiert. Es war eine Endlosrekursion.

    @Finnegan sagte in Pointer wegen möglichen Stackoverflow?:

    Ich nehme das also zurück, auch wenn ich denke, dass sowas doch eher selten vorkommt. Jedenfalls nicht oft genug um, die Heap-Allokationen für alles zu rechtfertigen (die bei der o.g. Endlosrekursion dann auch vor die Wand gelaufen wären, wenn auch vielleicht mit Out of Memory).

    Das sehe ich genauso. Das ist ein Sonderfall und deswegen sollte man nicht seinen ganzen Code umbauen.

    @SeppJ sagte in Pointer wegen möglichen Stackoverflow?:

    Das ist doch gerade ein Paradebeispiel dafür, wo der Blob sich darum gefälligst selber zu kümmern hat. Er hat ja auch kaum eine andere Wahl, denn entweder hält er seine Nutzdaten dynamisch, dann ist es sowieso auf dem Heap. Oder er hat ein char[1000000000] als Member in der Hoffnung, dass das für alle Blobgößen reicht, und dann gehört der Code weggeworfen und der Autor ins Gefängnis.

    Naja. Einer unserer Senjor Programmierer verwenden immer noch solche Konstrukte und meinte bei Smart Pointer hat man keine Kontrolle wann es gelöscht wird. Egal wie gut man ihm Smart Pointer erklärt hat.



  • @KK27 sagte in Pointer wegen möglichen Stackoverflow?:

    Naja. Einer unserer Senjor Programmierer verwenden immer noch solche Konstrukte und meinte bei Smart Pointer hat man keine Kontrolle wann es gelöscht wird. Egal wie gut man ihm Smart Pointer erklärt hat.

    Wenn der Zeitpunkt wirklich wichtig ist, dann kann man diesen anhand des Codes oder bei komplizierteren shared_ptr-Strukturen im Debugger exakt bestimmen und ggfs. "verschieben" indem man einfach etwas länger den letzten Pointer hält. Ich wage aber zu behaupten, dass das "wann" eher selten relavant ist, sondern vielmehr, dass der Speicher überhaupt freigegeben wird 🙂

    Vielleicht verwechselt der Kollege das "wann" auch mit der Gültigkeit des Objekts hinter dem Pointer. Die garantiert man aber nicht, indem man die Speicherfreigabe selbst kontrolliert, sondern indem man sicherstellt, dass man einen gültigen (Smart-) Pointer auf das Objekt hält, während man darauf zugreift. Da wird nichts freigegeben, solange der existiert (und nicht mit sowas wie ptr.release() explizit freigegeben oder irgendwie gemoved wurde).



  • @Helmut-Jakoby sagte in Pointer wegen möglichen Stackoverflow?:

    Wann ich Pointer nutze:
    Aus obigen Gründen und Pointer als Parameter, wenn eine Methode/Funktion ein Objekt als Parameter optional erwartet. Das übergebene Objekt muss natürlich nicht unbedingt im Heap angelegt werden.

    Solche Zeiger verwende ich auch häufiger bzw. als Referenzen wenn die Übergabe nicht optional ist oder das Objekt einen leeren Zustand hat.

    @Finnegan sagte in Pointer wegen möglichen Stackoverflow?:

    Wenn der Zeitpunkt wirklich wichtig ist, dann kann man diesen anhand des Codes oder bei komplizierteren shared_ptr-Strukturen im Debugger exakt bestimmen und ggfs. "verschieben" indem man einfach etwas länger den letzten Pointer hält. Ich wage aber zu behaupten, dass das "wann" eher selten relavant ist, sondern vielmehr, dass der Speicher überhaupt freigegeben wird
    Vielleicht verwechselt der Kollege das "wann" auch mit der Gültigkeit des Objekts hinter dem Pointer. Die garantiert man aber nicht, indem man die Speicherfreigabe selbst kontrolliert, sondern indem man sicherstellt, dass man einen gültigen (Smart-) Pointer auf das Objekt hält, während man darauf zugreift. Da wird nichts freigegeben, solange der existiert (und nicht mit sowas wie ptr.release() explizit freigegeben oder irgendwie gemoved wurde).

    Kann gut sein, dass dies mein Kollege meinte. Mit ein bisschen Übung meistert man das schnell. Am Anfang musste ich mich auch erst mit Smart Pointern anfreunden und lernen damit umzugehen.

    Ich hab jetzt meine Antworten zu dem Thema. Vielen Dank.



  • @KK27 sagte in Pointer wegen möglichen Stackoverflow?:

    Die Frage ist: Wie alt bist Du und wie alt ist der Kollege?

    Es gab früher CPUs am Markt, die nicht den kompletten Adressraum als Stack adressieren konnten. Daher wurde natürlich extrem sparsam mit dem Stack umgegangen, und die Standardeinstellung auf anderen CPUs war auch extrem gering für den Stack. D.h. nicht triviale Allokationen mussten immer auf dem Heap erfolgen. Das setzt sich halt im Kopf fest.


  • Gesperrt

    Na ja, ich kann zumindest für Java sprechen, dass Optionals eher Fluch denn Segen sind. An für sich war das Feature nicht nötig. Aber, in C war's In, also brauchte es Java auch... Wen es interessiert, hier mehr darüber.

    Ansonsten möchte ich mich an dem Kollegen-Bashing nicht beteiligen. Denn jeder weiß ja, dass studierte Leute in der Programmierung nichts taugen usw. Weg mit dem Abschaum. 😅



  • @john-0 sagte in Pointer wegen möglichen Stackoverflow?:

    Die Frage ist: Wie alt bist Du und wie alt ist der Kollege?

    Ich bin 29 Jahre alt. Der Kollege der alles mit Pointern macht und der Kollege der nicht vom unique_ptr überzeugt ist sind beide über 60 Jahren.



  • @KK27 sagte in Pointer wegen möglichen Stackoverflow?:

    Ich bin 29 Jahre alt. Der Kollege der alles mit Pointern macht und der Kollege der nicht vom unique_ptr überzeugt ist sind beide über 60 Jahren.

    Ok, für Dich etwas historischen Kontext, um das besser einordnen zu können, wie man früher Programmieren gelernt hat, da man in der Regel privat keinen Zugriff auf ein VMS System, UNIX System, IBM Mainframe o.ä. hatte.

    Die CPU von Apple II (außer IIGS), Commodore C16/C64/C128 und Atari 8Bit Rechner war entweder direkt ein MOS6502 oder ein Derivat davon. Diese CPU Familie war damals erschwinglich und sehr verbreitet, nur erlaubte diese CPU lediglich 255 Bytes als Stack für alle Routinen die gleichen 255 Bytes. D.h. trotz möglicher 1MB RAM Erweiterung (wahnsinnig teuer und nicht am Stück nutzbar, üblich waren 64kB im II/II+, und 128kB im IIe/IIc) für einen AppleII konnte man nur einen winzigen Stack nutzen. D.h. man nutzte ihn üblicherweise gar nicht.

    Bei anderen 8Bit CPUs (Z80, 8085, …) war der Stack auf 16Bits (64kB) limitiert, selbst wenn per Bank Switching bedeutend mehr RAM verbaut war.

    Damals sehr weit verbreitet waren DOS PCs. Die Intel 8088 CPU war ein Frickelfestival der Extraklasse, somit waren auch DOS und Windows (alles vor NT ist damit gemeint) ein Frickelfestival der Extraklasse. Ein 8086/8088 hat einen Adressraum von 1MB, aber nur wenn man das Indexregister (4Bit) zusätzlich zum Adressregister (16Bit) genutzt wird. Deshalb gibt es unterschiedliche Programmiermodelle mit unterschiedlichen Zeigerngrößen, die unter bestimmten Umständen zusammen genutzt werden. Beim 80286 kommen weitere Modi dazu, die aber unter DOS/Windos nicht genutzt wurden, weil sonst die alten Programme nicht mehr liefen.



  • @john-0 sagte in Pointer wegen möglichen Stackoverflow?:

    Ok, für Dich etwas historischen Kontext, um das besser einordnen zu können, wie man früher Programmieren gelernt hat, da man in der Regel privat keinen Zugriff auf ein VMS System, UNIX System, IBM Mainframe o.ä. hatte.

    Und heute schreiben wir Software für Funkchips in C, deren Programmgröße kleiner ist als 34 kByte, maximal 24 kByte RAM nutzt, mittels Bluetooth LE mit Handy kommuniziert, die Kommunikation zusätzlich Elliptic Curve Digital Signature Algorithm (ECDSA) nutzt, hierbei mehrere Crypto-Libs (Oberon, OPTIGA,..) unterstützt,...



  • ja solche alten sturköpfe, die "das schon immer so gemacht" haben sind echt furchtbar.

    andererseits kann man software, die ja seit 50 jahren "läuft", auch nicht mal eben so neu machen und da sollte man diese alten techniken dann schon beherrschen.


  • Mod

    Das ist ja keine alte Technik, sondern war schon genauso Quark vor 50 Jahren wie heute. Da hat jemand etwas über Heap und Stack gelesen, aber nur gefährliches Halbwissen mitgenommen. Das könnte heute genauso passieren, ist aber trotzdem kein Grund, das zu tolerieren



  • ja natürlich war das schon immer quark, aber ich behaupte jetzt einfach mal, dass es unmengen an code gibt, der genau so erstellt wurde, und dass man dann entweder ganz von vorne anfängt, oder eben weiter so rummurksen muss.

    dass letzteres auf dauer wahrscheinlich günstiger wird und ein in punkto geschwindigkeit und wartbarkeit viel effizienteres programm erzeugt, steht ja außer frage, aber erklär das mal jemandem mit entscheidungsbefugnis. https://www.youtube.com/watch?v=i3TA6-Hzw68

    davon mal ab: war das nicht so, dass man in c++ eigentlich gar keine zeiger mehr verwenden soll, weil die einfach total zum 🤮 sind?


  • Mod

    @Peter-Viehweger sagte in Pointer wegen möglichen Stackoverflow?:

    davon mal ab: war das nicht so, dass man in c++ eigentlich gar keine zeiger mehr verwenden soll, weil die einfach total zum 🤮 sind?

    Was ist denn das Ding, das auf deine dynamisch angeforderten Ressourcen zeigt? Nein, du wirst um Zeiger nicht herumkommen, denn Zeiger können Dinge, die nur Zeiger können.

    Es kann aber gut sein, dass du als high-level Programmierer damit nie in Kontakt kommst, weil eben alle Dinge, die Zeiger brauchen, diese intern benutzen und die Notwendigkeit vor dem Anwender wegkapseln. Das ist ja gerade der Fehler von KK27s Kollegen, dass er meint, das besser zu wissen, und sich damit nur das Leben selber schwerer macht.



  • @SeppJ sagte in Pointer wegen möglichen Stackoverflow?:

    Was ist denn das Ding, das auf deine dynamisch angeforderten Ressourcen zeigt? Nein, du wirst um Zeiger nicht herumkommen, denn Zeiger können Dinge, die nur Zeiger können.

    schon, aber

    Es kann aber gut sein, dass du als high-level Programmierer damit nie in Kontakt kommst, weil eben alle Dinge, die Zeiger brauchen, diese intern benutzen und die Notwendigkeit vor dem Anwender wegkapseln.

    ganz genau das meinte ich: da hat sich schon jemand gedanken gemacht, wie ich ohne zeiger auskomme.

    Das ist ja gerade der Fehler von KK27s Kollegen, dass er meint, das besser zu wissen, und sich damit nur das Leben selber schwerer macht.

    naja nicht nur sich, oder?



  • @Quiche-Lorraine sagte in Pointer wegen möglichen Stackoverflow?:

    Und heute schreiben wir Software für Funkchips in C, …

    Der Unterschied ist nur, dass man heute i.d.R. einen 32Bit Microcontroller nutzt.



  • @Peter-Viehweger sagte in Pointer wegen möglichen Stackoverflow?:

    davon mal ab: war das nicht so, dass man in c++ eigentlich gar keine zeiger mehr verwenden soll, weil die einfach total zum 🤮 sind?

    Ich denke die bessere Empfehlung ist, außerhalb von vielleicht selbst implementierten Containern und Smart Pointern in eigenem Bibliotheks-Code keine besitzenden Zeiger zu verwenden.

    Es kann ja durchaus mal nützlich sein, eine Referenz zu haben, die auch "nicht gesetzt" sein kann. f(T*) kann ich ja schliesslich auch mit einem Smart Pointer als f(ptr.get()) aufrufen, ohne dass dabei ein besitzender Zeiger weitergereicht werden muss.



  • @Finnegan Ich nutze nicht besitzende Pointer zwar auch noch, aber mit std::optional und std::reference_wrapper gibt es da ja auch schon Abstraktionmöglichkeiten, die sowas weg kapseln können.



  • @Schlangenmensch sagte in Pointer wegen möglichen Stackoverflow?:

    @Finnegan Ich nutze nicht besitzende Pointer zwar auch noch, aber mit std::optional und std::reference_wrapper gibt es da ja auch schon Abstraktionmöglichkeiten, die sowas weg kapseln können.

    Kann man machen. Ich frage mich aber, ob das dann aber nicht irgendwann Overkill ist. Solche Objekte sind ja schon etwas umständlicher zu verwenden als ein einfacher Pointer.

    Zugegeben, so ein std::optional wirft natürlich eine Exception, wenn ich auf einen nicht gesetzten Wert zugreifen will. Das ungeprüft zu tun ist aber m.E. ohenhin ein Programmfehler (was ich normal vial assert abfange), der meinentwegen auch mit einem Zugriffsfehler wegen Null-Pointer-Dererferenzierung aussteigen darf (auch wenn das streng genommen UB ist).

    Welche anderen Probleme kann damit vermeiden? Versehentliche oder fehlerhafte Pointer-Arithmetik? So ein T* wäre bei mir jedenfalls immer nur ein Objekt. Wenn ich mehrere haben will, dann verwende ich generische begin/end-Iteratoren oder neuerdings auch Ranges.

    Ich sage nicht, dass std::optional hier schlecht wäre, aber irgendwie will ich da auch nicht ganz auf Pointer verzichten, wegen der simpleren Anwendung. So ein std::optional<std::reference_wrapper<T>> ist schon ganz schön viel "Code-Rauschen", und wie gut das vom Compiler dann tatsächlich in Pointer-äquivalenten Code optimiert wird, müsste ich erstmal noch recherchieren 🤨



  • Im Gegensatz zu einem rohen Zeiger und std::reference_wrapper besitzt std::optional das gehaltene Objekt. Um den Zeiger/Wrapper benutzen zu dürfen muss das Objekt noch irgendwo leben, das ist bei std::optional nicht der Fall. Ein gutes Einsatzgebiet für std::optional sind zB Datenbankfelder, wo der Wert auch mal (null) sein kann, das kann man mit rohen Zeigern oder std::reference_wrapper schlecht modellieren.



  • Ich verfolge sehr interessiert diese Diskussion und habe eine Frage. Welches Konstrukt würde folgende Anforderung ggf. besser erfüllen:
    Eine Methode einer Klasse "ermittelt" ein Objekt (im konkreten Fall ein Objekt aus einer Datenbank) variabler Größe (kann ein Bild unbekannter Größe enthalten) und "liefert" dieses an eine aufrufende Instanz. Es können keine Annahmen getroffen werden, wie lange die aufrufende Instanz das Objekt "braucht".
    In alten Versionen habe ich der liefernden Methode als Parameter eine Referenz auf einen Zeiger geliefert und die liefernde Methode hat ein spezielles Objekt auf dem Heap erstellt und in der Referenz auf den Zeiger zurückgeliefert. In der Doku (in der Hoffnung dass diese gelesen wird 😉 ) habe ich darauf hingewiesen, dass die Verantwortung für das gelieferte Objekt bei der aufrufenden Instanz liegt.
    In den neueren Versionen wird von der liefernden Methode das zu liefernde Objekt in einem std::shared_ptr geliefert und das Problem mit dem zerstören des gelieferten Objektes scheint behoben zu sein.

    Zusätzlich stellt sich mir die Frage; wenn man z.B. mit Bibliotheken wie Qt arbeiten möchte oder muss, ist es da nicht von Vorteil wenn man über die Bedeutung von Zeigern Bescheid weiß?


Anmelden zum Antworten