Programm stürzt nach korrekter Ausgabe ab.
-
@Wade1234 sagte in Programm stürzt nach korrekter Ausgabe ab.:
naja es kostet eben 2 taktzyklen, den nullzeiger zu setzen, aber verhindert evtl. ganz furchtbare programmfehler, weil das programm dann ganz sicher (100%) abstürzt, weshalb es sich lohnt, sich das setzen von nullzeigern anzugewöhnen. also soweit ich weiß......
Nicht sehr weit wie es aussieht. Blöd nämlich dass der Compiler schlau ist und sieht dass er das mal locker flockig "as if" wegoptimieren kann. Weil nach der Zuweisung in einem konformen "well formed" C++ Programm einfach kein Zugriff auf die Variable mehr kommen kann.
Siehe
https://godbolt.org/z/gmlTwYint* dummy = new int(123); class Foo { public: Foo() : p(new int()) {} ~Foo() { delete p; p = dummy; } int* p = nullptr; }; void delFoo(Foo* f) { delete f; }
=>
delFoo(Foo*): test rdi, rdi je .L1 push rbp mov rbp, rdi mov rdi, QWORD PTR [rdi] test rdi, rdi je .L3 mov esi, 4 call operator delete(void*, unsigned long) .L3: mov rdi, rbp mov esi, 8 pop rbp jmp operator delete(void*, unsigned long) .L1: ret _GLOBAL__sub_I_dummy: sub rsp, 8 mov edi, 4 call operator new(unsigned long) mov DWORD PTR [rax], 123 mov QWORD PTR dummy[rip], rax add rsp, 8 ret dummy: .zero 8
Wie man hier sehr leicht sehen kann, ist die Zuweisung
p = dummy;
im generierten Assemblercode einfach weg.In Debug Builds sieht es anders aus. Macht die Sache aber nur noch schlimmer, denn dann hast du u.U. ein Programm das im Debug funktioniert und im Release crasht. Und keiner weiss wieso.
Wenn du wirklich den Zeiger ala "darf zwar nicht passieren aber sicher ist sicher" auf NULL setzen willst, dann musst du schon Kunstgriffe wie
volatile
bemühen.
-
Ist halt im Endeffekt sowieso alles hinfällig, weil man - wie in diesem Thread schon so oft gesagt - sowieso niemals mit Pointern auf diese Art und Weise herumfrickeln würde, sondern immer eine Art von Smartpointer oder anderen Ressourcenhalter benutzen würde. Deren interne Richtigkeit relativ einfach sicherzustellen ist, da die Komplexität überschaubar ist. Danach ist dann alles über das übliche Lebenszeitmanagement des Halterobjekts geregelt und die einzige Art und Weise, wie das dann überhaupt noch theoretisch schief gehen könnte, wäre der unvorstellbare Fall, dass der Compiler selbst defekt ist.
Außer natürlich, man hat es mit einem Lehrer zu tun, der programmiert, als wäre es 1972. Dabei wurde C++ buchstäblich erfunden, damit man eben nicht so zu programmieren braucht.
Diese dumme Aufgabenstellung reizt mich so sehr, dass ich geneigt bin, dem TE einfach die Hausaufgaben zu machen, aber eben mit einem selbstgeschriebenem Vector als unterliegender Datenstruktur...
-
@hustbaer sagte in Programm stürzt nach korrekter Ausgabe ab.:
Wenn du wirklich den Zeiger ala "darf zwar nicht passieren aber sicher ist sicher" auf NULL setzen willst, dann musst du schon Kunstgriffe wie
volatile
bemühen.dass der compiler das erstmal als unnötig rausoptimiert, ist klar, weil danach ja nichts mehr passiert. wenn du das programm aber irgendwann mal erweiterst und dann weiter unten den nullzeiger dereferenzierst, bleibt das doch im code drin und das programm stürzt bei gängigen betriebssystemen ab.
-
@Wade1234 Ach du meinst wenn man aus welchem Grund auch immer die grässliche Variante ganz ohne Smart-Pointer macht, wo es dann Klassen gibt die mehr als eine Resource besitzen bzw. "nebenher" auch noch was anderes machen? Ja, in dem Fall sollte man das machen. Nur ... sollte man so halt generell nicht programmieren. Ganz egal ob (vordefinierte) Smart-Pointer jetzt erlaubt sind oder nicht - dann schreibt man sich halt ggf. nen einfachen Smart-Pointer schnell selbst (muss ja meist nicht viel können).
Und selbst definierte Smart-Pointer zu verbieten kann mMn. nur einen Grund haben: den Leuten vor Augen zu führen wie scheisse alles wird wenn man sie nicht verwendet. z.B. indem man sie erstmal den Code schreiben lässt, dann zeigt was bei ihrem Code alles schief gehen kann, und ihnen dann zeigt wie man es ohne Smart-Pointer fixt, wie ekelig der Code dadurch wird und wie einfach und elegant es geht wenn man Smart-Pointer verwendet. (Und Smart-Pointer steht hier natürlich stellvertretend für sämtliche Resourcen bzw. generell Dinge wo ein "undo" für jedes "do" erforderlich ist.)
Ich dachte wirklich du meinst das direkt so im Dtor als letzte Anweisung, und in Klassen wo klar ist dass sie nicht erweitert werden. Mit Kommentaren dabei ala "prevent problems with double deletion" oder so. Und das mag OK sein wenn man mal um nen Compiler-Bug rumarbeiten muss, aber generell halte ich es halt für ne doofe Idee.
-
@hustbaer ja genau falls man mal wissen möchte, wie es in so ganz alten programmen aussehen könnte und die netten modernen features nicht zur verfügung stehen.
dass man das bei neuen projekten ein bisschen anders machen könnte, weiß ich auch, aber allgemein ist man in den unternehmen wohl nicht so begeistert, wenn da irgendson absolvent kommt und meint, dass man das programm, an dem die letzten 20 jahre gearbeitet wurde, ja einfach mal neu und viel besser und unkomplizierter machen könnte.
jedenfalls hat man uns damals so erklärt, warum wir eigentlich C mit erweiterungen lernen.
-
Das hat weniger mit "alt" gegen "modern" zu tun und mehr mit "gut" gegen "scheiße". Die Idee von Ressourcenhaltung mittels spezieller Klassen, Trennung von Zuständigkeiten, etc. sind nun wahrlich keine neuen Ideen. Diese Ideen sind sogar deutlich älter als C++ selber (und C++ ist uralt!). Tatsächlich war die treibende Hauptidee hinter C++, dass man ein Framework für C wollte, mit dem man diese Ideen in C mit wenig Aufwand umsetzen kann (Denn ein guter Programmierer programmiert auch in C auf diese Weise. Es ist bloß recht viel Schreibarbeit, die man sich in C++ sparen kann). Es gibt und gab halt trotzdem viele Leute, die es einfach nicht drauf haben und in egal welcher Sprache nur Mist produzieren.
Dass sich das in modernen Zeiten besser anfühlt, mag daran liegen, dass mittlerweile die Fraktion derer, die kapiert haben, wie C++ gedacht ist, so überwältigend laut ist, dass man schon echt hinterm Wald wohnen muss, um den Schuss nicht gehört zu haben. Wie offenbar der Prof, der hinter dieser Aufgabe steht.
-
@Quiche-Lorraine Danke, habs mir angeguckt. Ich versuch so viel wie möglich mitzunehmen. Nur grade hab ich gefühlt einen Tiefpunkt beim Lernen erreicht. Ich hab viel mit den Unterlagen aus der Vorlesung und parallel mit einem Buch gearbeitet. Dabei lief es gefühlt auch ganz gut (nebenbei auch immer mal was geschrieben, anstatt nur zu lesen). Jetzt mit den Aufgaben fühlt es sich aber eher so an, als wenn ich gar nichts verstanden habe
@SeppJ Ich verstehe wirklich nicht, wie du dich die ganze Zeit an dieser einen Aufgabe so aufziehen kannst. Zudem ist es doch auch recht anmaßend, wie du dich über den Prof äußerst. Was nicht heißt, dass ich hier jemanden in Schutz nehmen möchte. Nur reicht ein kleiner Ausschnitt aus einer Aufgabenstellung meiner Meinung nach nicht aus, um sich ein vernünftiges Urteil über die Art und Weise, wie jemand lehrt zu bilden.
-
@Sedna sagte in Programm stürzt nach korrekter Ausgabe ab.:
Nur reicht ein kleiner Ausschnitt aus einer Aufgabenstellung meiner Meinung nach nicht aus, um sich ein vernünftiges Urteil über die Art und Weise, wie jemand lehrt zu bilden.
Doch, der Ausschnitt reicht völlig.
-
@Sedna sagte in Programm stürzt nach korrekter Ausgabe ab.:
Jetzt mit den Aufgaben fühlt es sich aber eher so an, als wenn ich gar nichts verstanden habe
Frag einfach nach, wenn du bestimmte Dinge nicht verstanden hast! Wie du gemerkt hast, ging die gesamte Kritik hier bisher auch an die Aufgabe/den Aufgabensteller, nicht an dich. Gut, immerhin lernst du jetzt wenigstens gleich, warum das so keine gute Idee ist
Ich verstehe wirklich nicht, wie du dich die ganze Zeit an dieser einen Aufgabe so aufziehen kannst.
Das liegt daran, dass viel zu viele Studenten das eben genau so lernen, weil es eben so gelehrt wird. Und man dann jedem neuen Entwickler erst einmal erklären muss, dass das so NICHT gemacht wird, wenn man ordentlich arbeiten will. Und erfahrungsgemäß ist die nächste Aufgabe eben nicht "wie macht man es besser", sondern was ganz anderes. Vielleicht auch, weil in der Veranstaltung eigentlich gar nicht C++ gelehrt werden soll, sondern nur irgendeine Sprache als Mittel zum Zweck verwendet wird und zufällig altes Lehrmaterial für C++ rumlag. Nur wird man, wenn man so in C++ arbeitet, schnell Probleme bekommen.
-
Ich verstehe @SeppJ s Aufregung schon. Ich hab einige Arbeitskollegen die grosse Augen bekommen und neugierig fragen "ja wie macht man das denn sonst" wenn ich anmerke dass es Sch*** ist in einer Klasse direkt 2, 3, 5, 10 Resourcen zu besitzen. Allein schon weil den Ctor leak-frei und exceptionsicher zu bekommen ein Albtraum ist.
-
@SeppJ sagte in Programm stürzt nach korrekter Ausgabe ab.:
dem TE einfach die Hausaufgaben zu machen, aber eben mit einem selbstgeschriebenem Vector als unterliegender Datenstruktur
Das seh ich auch so... Hier wäre mein Vorschlag mit
char *
anstattstring
und ohnevector
:#include <cstring> #include <iostream> using namespace std; class Student { public: Student(const char *name, int matrikelnummer, int semester) { this->name = new char[strlen(name)]; for (size_t i = 0; name[i]; i++) { this->name[i] = name[i]; } this->matrikelnummer = matrikelnummer; this->semester = semester; } ~Student() { delete this->name; this->name = nullptr; this->matrikelnummer = 0; this->semester = 0; } Student(const Student &s) { this->name = new char[strlen(s.name)]; for (size_t i = 0; s.name[i]; i++) { this->name[i] = s.name[i]; } this->matrikelnummer = s.matrikelnummer; this->semester = s.semester; } void setSemester(int semester) { this->semester = semester; } friend ostream &operator<<(ostream &os, const Student &s); private: char *name; int matrikelnummer; int semester; }; ostream &operator<<(ostream &os, const Student &s) { os << s.name << " ; " << s.matrikelnummer << " ; " << s.semester; return os; } void printKlausAndGundula() { Student stud = Student("Kleber, Klaus", 1234, 1); Student stud2 = Student("Gauss, Gundula", 5678, 1); Student stud3 = stud2; stud2.setSemester(3); cout << stud << endl; cout << stud2 << endl; cout << stud3 << endl; } int main() { while (true) { printKlausAndGundula(); } }
-
@EinNutzer0
Hast du den Code mal laufen lassen? Das sollte wunderschön wegcrashen so wie du es hier gepostet hast.EDIT: Ne, sorry, du musst noch ein bisschen was ändern damit es crasht.
EDIT2: strlen Bug übersehen, d.h. ja doch, hat auch so gutes Crash-Potential.
-
Das behebt jetzt genau 1 Problem, nämlich dass die Namen der Studenten erhalten bleiben, wenn du bei strlen + 1 nicht vergessen hättest (nebst kopieren des Nullbytes). Wozu eine Library-Funktion wie strcpy nutzen, wenn man es auch selbst falsch nachimplementieren kann und wenn es mit std::string 1000x einfacher wäre?
Rule of Zero / Three / Five?
~Student() { delete this->name; this->name = nullptr; this->matrikelnummer = 0; this->semester = 0; }
Das sind absolut nutzlose 3 Zeilen überflüssiger und entbehrlicher Code, der nutzlos ist und weder gebraucht noch verwendet wird
Und außerdem: die zum Kleber-Claus gehörige Gundula heißt "Gause".
-
Habe noch einen Default Constructor hinzufügt:
#include <cstdlib> #include <cstring> #include <iostream> using namespace std; class Student { public: Student() : Student("default constructor", 1, 1) { } Student(const char *name, int matrikelnummer, int semester) { this->name = new char[strlen(name) + 1]; for (size_t i = 0; name[i]; i++) { this->name[i] = name[i]; } this->matrikelnummer = matrikelnummer; this->semester = semester; } ~Student() { delete this->name; this->name = nullptr; this->matrikelnummer = 0; this->semester = 0; } Student(const Student &s) { this->name = new char[strlen(s.name)]; for (size_t i = 0; s.name[i]; i++) { this->name[i] = s.name[i]; } this->matrikelnummer = s.matrikelnummer; this->semester = s.semester; } void setSemester(int semester) { this->semester = semester; } friend ostream &operator<<(ostream &os, const Student &s); private: char *name; int matrikelnummer; int semester; }; ostream &operator<<(ostream &os, const Student &s) { os << s.name << " ; " << s.matrikelnummer << " ; " << s.semester; return os; } void printKlausAndGundula() { const int n = 5000; Student stud = Student("Kleber, Klaus", 1234, 1); Student stud2 = Student("Gauss, Gundula", 5678, 1); Student stud3 = stud2; stud2.setSemester(3); cout << stud << endl; cout << stud2 << endl; cout << stud3 << endl; Student studarr[n]; for (size_t i = 0; i < n; i++) { int r = rand() / (RAND_MAX / 3); switch (r) { case 0: studarr[i] = stud; break; case 1: studarr[i] = stud2; break; case 2: studarr[i] = stud3; break; default: break; } } cout << studarr[0] << endl; cout << studarr[1] << endl; // Aborted (Speicherabzug geschrieben) cout << studarr[n - 2] << endl; cout << studarr[n - 1] << endl; }
Es crasht und ich weiß nicht wieso?
Habe auch
+ 1
hinzugefügt...
-
-
@EinNutzer0 dein eigenes
strcpy()
kopiert die '\0' am Ende nicht.
-
Danke, nu funktioniert s, es ist aber noch nicht richtig, hab das
delete
entfernt...#include <cstdlib> #include <cstring> #include <iostream> #include <memory> using namespace std; class Student { public: Student() : Student("default constructor", 1, 1) { } Student(const char *name, int matrikelnummer, int semester) { this->name = new char[strlen(name) + 1]; strcpy(this->name, name); this->matrikelnummer = matrikelnummer; this->semester = semester; } ~Student() { this->name = nullptr; this->matrikelnummer = 0; this->semester = 0; } Student(const Student &s) { this->name = new char[strlen(s.name) + 1]; strcpy(this->name, s.name); this->matrikelnummer = s.matrikelnummer; this->semester = s.semester; } void setSemester(int semester) { this->semester = semester; } friend ostream &operator<<(ostream &os, const Student &s); private: char *name; int matrikelnummer; int semester; }; ostream &operator<<(ostream &os, const Student &s) { os << s.name << " ; " << s.matrikelnummer << " ; " << s.semester; return os; } void printKlausAndGundula() { const int n = 5000; Student stud = Student("Kleber, Klaus", 1234, 1); Student stud2 = Student("Gauss, Gundula", 5678, 1); Student stud3 = stud2; stud2.setSemester(3); cout << stud << endl; cout << stud2 << endl; cout << stud3 << endl; Student studarr[n]; for (size_t i = 0; i < n; i++) { int r = rand() / (RAND_MAX / 3); switch (r) { case 0: studarr[i] = stud; break; case 1: studarr[i] = stud2; break; case 2: studarr[i] = stud3; break; default: break; } } cout << studarr[0] << endl; cout << studarr[1] << endl; // Aborted (Speicherabzug geschrieben) cout << studarr[n - 2] << endl; cout << studarr[n - 1] << endl; }
@manni66 sagte in Programm stürzt nach korrekter Ausgabe ab.:
Dadurch wird der Speicher von name am Ende zweimal freigegeben
Daran wird es liegen...
-
@wob sagte in Programm stürzt nach korrekter Ausgabe ab.:
Und außerdem: die zum Kleber-Claus gehörige Gundula heißt "Gause"
Ja tut mir leid, schau kein ZDF
-
Durch das Entfernen des
delete
hast du ein Memory Leak.
Und es mussdelete[] this->name;
lauten, denn du allozierst ja ein Array.Wenn du mal in The rule of three/five/zero schaust, dann wirst du sehen, daß dir noch eine Operation fehlt, welche du implementieren mußt (Hint: Copy-and-swap).
Und da du (dann) sicherlich bemerkt hast, daß das viel zu viel Arbeit für eine relativ einfache Funktionalität ist, solltest du dir unbedingt RAII aneignen, d.h. wenn schon nicht die
std:.string
benutzt wird, dann wenigstens eine eigene Helperklasse benutzen, welche Ressourcenerzeugung und -löschen durchführt (das dann auch wieder exceptionsicher ist).
-
ja... zu komplex, du hast recht... Genug für heute, bis morgen!