[C++] Wie kann ich herausbekommen ob ein gültiges Objekt an einem Pointer hängt?

  • Ersteller Ersteller Green Mamba
  • Erstellt am Erstellt am
G

Green Mamba

Gast
Hi,

ich hab hier momentan ein Problem mit Qt und Coin3D. Ich erzeuge Objekte an Pointern, und übergebe diese. Zu einem Zeitpunkt, den ich noch nicht einkreisen konnte verschwinden jedoch einige dieser Objekte.
Gibt es eine Möglichkeit im Sourcecode festzustellen ob ein gültiges Objekt zu einem Zeitpunkt hinter dem Pointer hängt? Ich meine einen C oder C++-Befehl. Mit meinem Debugger-Anzeigen komm ich nicht wirklich klar, da diese teils zweideutig sind. Es muss doch da eine universelle möglichkeit geben!?
Sourcen kann ich leider nicht zur Verfügung stellen, da viel zu umfangreich.

Viele Grüße,
Green Mamba
 
Nunja, ein cleverer Programmierer kennzeichnet sich Pointer-Variablen in dem er beispielsweise jeden Pointer mit 'p_' beginnt.
Ansonsten wirds schwierig, am Ende noch Pointer von echten Variablen oder Objekten zu unterscheiden. Dazu musste dann wohl den Quellcode von vorne bis hinten durchwühlen - bei mehr als 200 Zeilen Quellcode kein wirkliches Vergnügen mehr.


Darüber hinaus weiss jeder, dass Pointer und Setter (Getter leider nicht) Hunderassen sind, die als Objekte nur Bäume und Hydranten kennen. :D
 
Ich glaub ich hab mich etwas unglücklich ausgedrückt. Ich weiß schon was der Bezeichner des Pointers ist, allerdings will ich wissen worauf er zeigt. Hängt ein gültiges Objekt dahinter, oder zeigt er ins Nirvana!?
Ich hoffe jetzt ist es verständlich. :)
 
Da verweise ich doch ganz fröhlich auf http://doc.trolltech.com/3.3/qguardedptr.html :).

Mit reinen C++ Mitteln ist da absolut nichts zu machen.
Wenn das Objekt am Zeiger verschindet, dann merkt der Zeiger nichts. Er zeigt weiter auf die Speicheradresse und nervt dich dann mit unerlaubten Speicherzugriffen.

Das Stichwort hierzu ist "Smart Pointer". Der QGuardedPtr ist eine mögliche Implemetierung dieser Technik.
 
Mist, ich hab mir schon sowas gedacht. Leider sind die Objekte die für mich wichtig sind ausnahmsweise nicht von Qt direkt, sondern von SoQt, und von Coin3D.
Ich hatte gehofft es gibt ne Methode ala "Hallo du Objekt da, bist du noch ganz frisch?". :D
Ich bin auch unsicher ob es wirklich am verschwundenen Objekt liegt, oder ob es erst beim betreten der QApplication.exec(); bzw SOQT::mainloop(); knallt. :freak:

Ich erzeuge in meiner main eine Qt-GUI, dann einen ProgramManager, dem ein Pointer auf die GUI übergeben wird. Im ProgramManager wird einem ExaminerViewer (OpenGL), ein Szenengraph übergeben.
Das geschieht entweder über eine Signal-Slot-Verbindung, also über einen Button, oder testweise auch gleich vom der Main aus. Beim anschließenden betreten der mainloop krachts dann mit einem Callstack der augenscheinlich nichts mehr mit dem eigentlichen Programm zu tun hat. :freaky:

Die mainloop wird entweder vor dem einhängen des Szenengraphen betreten (Signal-Slot), oder eben danach (direkter Aufruf aus der Main).
Wenn ich mit dem debugger schritt für schritt durch das Programm gehe, gibts den Crash eben erst beim Aufruf der Mainloop. QApplication.exec(), und SOQT::mainloop(); sollten dabei ungefähr äquivalent sein.

Die main kann ich ja mal anhängen:
Code:
#include "ProgramManager.h"
#include <qapplication.h>
#include "FlexGUI_Base.qt.h"
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include "ProgramManager.h"
#include <qwidgetstack.h>
#include <qtabwidget.h>
#include <Inventor/nodes/SoSeparator.h>

int main (int argc, char** argv)
{
  QApplication app (argc, argv);
  FlexGUI_Base* baseGUI = new FlexGUI_Base ();
  app.setMainWidget (baseGUI);
  SoQt::init ( app.mainWidget() );
  baseGUI->show();
  QWidget* viewerFrame = new QWidget ();
  SoQtExaminerViewer* Viewer = new SoQtExaminerViewer( viewerFrame );

  baseGUI->widgetStackViewer->addWidget (viewerFrame, 1);
  baseGUI->widgetStackViewer->raiseWidget(1);

  SoSeparator* root = new SoSeparator;
  Viewer->setSceneGraph(root);

  ProgramManager* manager = new ProgramManager (baseGUI, Viewer, root);
  manager->start ();

  app.exec();
  //SoQt::mainLoop();

  delete Viewer;
}
 
Ich selber habs noch nicht probiert, müsste das nicht aber auch mit dynamic_cast<> als Qt unabhängige Variante funktionieren? Der überprüft doch zur Laufzeit, ob es dem Objekttyp ist, den man angibt, ansonsten liefert der NULL zurück.
Also:
CKlasse *pPointer;
if(dynamic_cast<CKlasse*>(pPointer)) cout << "juhu, gültiger Pointer" << endl;
Eigentlich ist das ja für Up-/Down Casts da, aber warum sollte der bei gleichem Typ die Arbeit verweigern...
Vielleicht nicht die schönste Variante, funktioniert auch nur wenn der Compiler nicht von vorgestern... Ein Versuch wärs aber Wert
 
Ich werd mir das mit dem Cast mal ansehen. Das ist mal wirklich ne geniale Idee. :daumen: Hier mal noch der Callstack:
Code:
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1101402688 (LWP 5004)]
0x40b4067e in _int_malloc () from /lib/tls/libc.so.6
(gdb) bt
#0  0x40b4067e in _int_malloc () from /lib/tls/libc.so.6
#1  0x40b42b14 in calloc () from /lib/tls/libc.so.6
#2  0x408150ce in glXChannelRectSyncSGIX () from /usr/lib/libGL.so.1
#3  0x00000001 in ?? ()
#4  0x00001b24 in ?? ()
#5  0x4176ccf0 in _nv000845gl () from /usr/lib/libGLcore.so.1
#6  0x00000001 in ?? ()
#7  0x00001b24 in ?? ()
#8  0x00000001 in ?? ()
#9  0x00000008 in ?? ()
#10 0x421c2040 in ?? ()
#11 0xbfffd738 in ?? ()
#12 0x40016fec in ?? () from /lib/ld-linux.so.2
#13 0x4220e2d8 in ?? ()
#14 0x4220e28c in ?? ()
#15 0x4220e2d8 in ?? ()
#16 0x4000cc26 in fixup () from /lib/ld-linux.so.2
Previous frame inner to this frame (corrupt stack?)
(gdb)

corrupt stack?
Was soll das heißen? Komischerweise bin ich eigentlich in einem Qt- bzw. Coin3D-Kontext. :freak:
 
Zuletzt bearbeitet:
Corrupt Stack... da hast Du wohl über deinen eigenen Stackrahmen hinweggeschrieben :) Lass doch mal valgrind drüberlaufen. Wenn sowas passiert, ist normalerweise Stack und alles weiter verwertbare geschrottet.

@Bombwurzel: Ungarische Notation hat sich schon seit einiger Zeit als Irrweg herrausgestellt, sie verbessert die Lesbarkeit kaum, im Gegenteil.

@dynamic_cast: Das ist kein gültiges C++. Der Zeiger ist ungültig, also ist der dynamic_cast verboten. Undefiniertes Verhalten ist undefiniert!

@smartpointer: Das ist der Weg, den Du gehen solltest.
 
Hallo Green Mamba,

mal als allgemeine Anmerkung.

Ich sehe hier viele new Operatoren und keinerlei Sicherheitsabfragen ob die Pointer auch gültig (!= NULL) sind.

Wenn sich das durch den ganzen Code zieht sind mit Sicherheit Probleme zu erwarten. Insbesondere gilt das für Bereichsüberschreitungen von Arrays (Strcpy, memcpy,...).

Ansonsten wäre ein Ansatz den new und delete Operator zu überladen und vor und nach dem eigentlichen Speicherbereich Marker zu setzen (z.B. 0xABBA am Anfang und 0xCDDE am Ende des Speicherbereichs). Der new Operator gibt einen Pointer auf den Speicher zwischen den Markern zurück.

Was dann noch fehlt ist eine Überprüfung ob einer dieser Marker überschrieben worden ist.
Dazu musst Du eine Liste aller mit new erstellten Objekte führen und diese regelmässig prüfen. Durch extensives setzen von Prüfstellen kannst Du dem Problem damit auf die Spur kommen.

Als erstes würde ich aber mal alle Speicheroperationen (mem...,str...) durchgehen.

MfG

Arnd
 
Arnd schrieb:
Ich sehe hier viele new Operatoren und keinerlei Sicherheitsabfragen ob die Pointer auch gültig (!= NULL) sind.

Die Pointer sind gültig. Eine Prüfung auf 0 (NULL in C++ -> veraltet) ist überflüssig und unschön. Das liegt daran, dass der Standard definiert, dass falls die Speicheralloziierung fehlschlägt eine std::bad_alloc-Exception geworfen wird. Nur wenn man einen new_handler installiert hat oder ohne Exceptions arbeitet, könnte sowas evtl nötig werden. Davon würde ich mich aber immer distanzieren.


@GM: Versuch doch mal stumpf valgrind auf das Problem zu werfen, damit wirst Du schon sehr wahrscheinlich SBRs/ABRs (Stack-/Array-Bound-Reads) finden, sofern das denn überhaupt das Problem ist. Das andere mit dem potenziellen delete... wie gesagt... versuch mal SmartPointer.
 
Hallo 7H3 N4C3R,

das setzt dann aber ein gültiges Exceptionhandliung voraus.
Das ist aus dem vorliegenden Code nicht ersichtlich.

Ausserdem empfinde ich ein Programm das einfach nur abstürzt nicht als betriebssicher.
Ob es das nun im Exceptionhandling tut oder im Anwendercode spielt doch nun wirklich keine Rolle?

Ich erwarte eine saubere Fehlermeldung für den Endanwender dem gesagt wird was fehlgeschlagen ist. Und eine saubere Reaktion auf den Fehler. Das kann ein automatisches Exception Handling selten liefern.

Von daher kann ich die Ansicht das die Abfrage von NULL Pointern veraltet ist nicht nachvollziehen. Die Definition des Standards bietet doch nur eine definierte Schnittstelle um NULL Pointer zentral abfangen zu können.

Was ist dann mit überschriebenen Pointern oder solchen die unabsichtlich auf NULL gesetzt werden.

Von daher sind Sicherheitsabfragen aus meiner Sicht weiterhin essentiell für stabile Programme!

Zum Beispiel möchte ich als Nutzer einer Methode jederzeit den Wert NULL reinschieben können und erwarte dann eigentlich das es keinen Absturz sondern einen Fehlercode als Rückgabe gibt.
Bei der Grundannahme das Sicherheitsabfragen auf NULL Pointer unschön sind, kann man das wohl nicht erwarten?

Und ob ich nun jeden Codeblock in try catch Blöcke stecke oder mit if Abfragen arbeite das Ergebnis ist das selbe. Was dann schöner ist darf sich dann jeder selber aussuchen.

MfG

Arnd
 
Zuletzt bearbeitet:
Hallo Arnd,

die Gültigkeitsprüfung auf != 0 war selbstverständlich nur auf die Zeiger bezogen, die dir new zurückliefert (unter der Annahme, es gibt kein überladenes new). Entwedet Du bekommst einen gültigen Zeiger oder new wirft eine Exception - da kann man sich drehen und wenden wie man will. An der Stelle stellt sich die Frage, was Dir lieber ist - ein verschleppter 0-Zeiger, der vielleicht erst viel später entdeckt wird und zu einem schwer nachvollziehbaren Absturz führt - oder eine Exception an genau dem Punkt wo der Fehler aufgetreten ist? (beim Debuggen lernt man diesen Vorteil zu schätzen, außerdem handelt es sich zu 95% um reine Programmierer-Fehler) Und Murphys Gesetz wird immer zuschlagen - new wird werfen, ob Du es willst oder nicht. Wenn nicht aufgrund von Speichermangel, dann innerhalb eines Konstruktors oder sonstwo. C++ kennt und hat Exceptions - du musst immer darauf gefasst sein, dass eine geworfen wird.

Deine Ansichten zu return-Codes und 0-Zeigern mögen aus Sicht eines C-Programmierers okay sein - unter C++ sind sie aber nur noch sehr sehr schwer zu legitimieren. Wenn ich nicht will, dass eine Funktion/Methode 0-Zeiger bekommt, verwende ich Referenzen. Die Rückgabe von return-codes für Fehler... die meisten Fehler, die ich bis jetzt gesehen habe basieren darauf, dass stumpf return-codes ignoriert wurden. Mit einer Exception zwingst Du den Aufrufer zu einer Bearbeitung und dazu, anständigen Code hinzuschreiben. Anderenfalls stürzt sein Programm ab und er sollte sich mal dringend Gedanken machen, was er falsch gemacht hat. Noch ein Einwurf nebenbei zur Benutzerinformation - wie soll eine Routine tief im Programm, die nichts von Anwender oder GUI weißt, eine Fehlermeldung ausgeben? Mühsames durchschleifen und pflegen von zig aufrufenden Routinen ist die bittere Pille, die man hier schlucken muss. Gerade wenn man eine Bibliothek schreibt und diese mal ändern muss, ist das für Benutzer dieser Bibliothek eine ziemlich große Bürde.

Returncodes behalten natürlich sicherlich auch ihre Daseins-Berechtigung. Längst nicht alles rechtfertigt eine Ausnahme. Exceptions sind schließlich kein Kommunikationsmittel innerhalb der Anwendung sondern signalisieren Ausnahmesituationen. Es kommt immer auf die konkrete Situation an.

Und noch eine Anmerkung - Fehler in Konstuktoren kannst Du nicht anders signalisieren als über eine Exception. Konstruktoren haben nunmal keinen Rückgabewert. Und es ist oft nicht möglich, ein Objekt sinnvoll weiter zu erzeugen nach einem Fehler. Z.B. dann, wenn kein Speicher mehr da ist. Es wird also eine Exception geworfen, die Konstruktion wird abgebrochen und das Objekt hat nie gelebt.
 
Zuletzt bearbeitet:
man sollte an dieser stelle noch den riesen vorteil von exceptions gegenüber error-code-rückgabe anspreche. exceptions räumen gleich alles auf, bis man sie abfängt. wenn man in einer unterprozedur eine ausnahme wirft, wird der stack zurückgerollt bis ein handler gefunden wird, auf dem weg bis zum handler werden von allen lokalen und temporären objekten die destruktoren auf gerufen. wenn man nun halbwegs geschickt programmiert hat, kann man die gesamt aufräumarbeit dem programm selbst überlassen. was sich insbesondere bei bliliotheken als sehr praktisch erweisen kann.
das setzt natürlich voraus, dass alle wichtigen dinge in objekte gekapselt hat und statt zeigern smartpointer o.Ä. benutzt werden, aber das ist eine eher geringer aufwand für die viele arbeit die man sich spart.
daher bin ich ein verfechter von exceptions. sie machen das leben einfach viel leichter. :)
 
Hallo 7H3 N4C3R,

ich rede ja gar nicht gegen Exceptions. Sie sind mir auch herzlich willkommen. Wenn es sinnvoll ist nutze ich sie auch.

Sie sind nur keine Entschuldigung dafür auf jegliche Sicherheitsabfragen zu verzichten und sich dann zu wundern das es später Probleme gibt.

Das es unter Ausnutzung aller C++ Features und auch deren konsequenter Anwendung man auf manche ifs verzichten kann ist ok.

Aber mal zum Beispiel:

try
...
catch(CException* e)
{
// Das schreit aus meiner Sicht hier nach einem if
// auch wenn das viele vielleicht anders sehen :-).
if( e )
{
...
}
}

Und alle returncodes durchzuschleifen ist auch nicht notwendig wenn die Struktur der Lib intelligent designed ist. Ich möchte hier auch keine Abhandlung über Fehlermanagement lostreten.

Ich wollte nur mal anmerken, das man bei Stack/Heap Problemen mal über Fehlerbehandlung nachdenken sollte.

Und der obige Code hat dieses eben vermissen lassen. Ich nehme dann einfach an das der restliche Code im selben Programmierstil verfasst ist.

Und Du hast Recht das meine Ansichten von C her kommen, schliesslich habe ich das zuerst gelernt. Aber überflüssig geworden sind sie deswegen auch noch nicht.

Man kann/sollte sicher beides verwenden. Hauptsache ist das man es verwendet :-).

MfG

Arnd
 
try
...
catch(CException* e)
{
// Das schreit aus meiner Sicht hier nach einem if
// auch wenn das viele vielleicht anders sehen :-).
if( e )
{
...
}
}

nein, dass schreit nicht nach einem if. da du dank c++ typsicherung schon was in der richtung von:
throw ((CException*) 0) oder
CException * ex;
throw ex;
machen müsstest, um eine nicht legalen zeiger zu erzeugen.
das tut man aber nicht, da man in der exception daten mit liefern kann und es auch tut, da es sonst keinen sinn macht eine exception zu werfen. du kannst ja bekanntlich werfen was du willst, also kannst du einen fehlerreport für den nutzer und einen fehlercode für das programm und was immer du sonst noch brauchst in die exception packen, daher ist es reichlich sinnlos einen NULL-pointer zu werfen, vor allem weil es derjenige, der deine bibliothek oder was auch immer, nutz nicht erwartet, dass so was passiert.
 
Arnd/ghorst schrieb:
try
...
catch(CException* e)
{
// Das schreit aus meiner Sicht hier nach einem if
// auch wenn das viele vielleicht anders sehen :-).
if( e )
{
...
}
}
Sowas macht man ganz grundsätzlich nicht!
Code:
#include <iostream>
#include <stdexcept>

using cout;
using endl;

....


try {
  throw std::runtime_error( "Hilfe, ein Fehler");
}
catch( const std::exception& e) {
  cout << e.what() << endl;
}
catch( ...) {
  cout << "Hilfe, ein unbekannter Fehler!" << endl;
}

Mit Zeigern um sich werfen macht man niemals. Und Zeiger auffangen auch nicht.
 
Hallo,

ich kann mich aber entsinnen das in der DAO von MS so etwas gemacht wird.
Ist aber schon ein paar Jahre her.

Und einfach noch mal grundsätzlich das man so etwas nicht macht ist schon richtig.

Nur das weiss nicht jeder und vor allem nicht jeder Hersteller einer Klasse oder einer Bibliothek. Vor allem die Software von Microsoft ist in dieser Hinsicht sehr fehleranfällig.

D.h. um fehlertolerante Software zu schreiben, sind auch in der Zeit von C++ Überprüfungen von Zeigern essentiell.

Das man Software schreiben kann die ohne Zeiger auskommt bestreite ich ja auch nicht. Nur wenn sie vorhanden sind, dann können sie eben auch fehlerhaft sein oder eben NULL enthalten.

Ein Program oder eben Klasse, Methode ... sollte doch mit jeder Art von Input zurecht kommen ohne abzustürzen und sollte tunlichst keinen Output produzieren durch den andere Teile abstürzen.

MfG

Arnd
 
Soo, nach längerem herumprobieren hab ich dann endlich den eigentlichen Fehler gefunden. Und zwar hätte ich nicht SoQt::init nach dem erstellen einer QApplication aufrufen dürfen. So hatte ich 2 Instanzen die beide die Events abgehört haben. Dadurch kommt es wohl zu absolut undefiniertem Verhalten. Dieses Eventhandling in Hintergrund umfasst auch die atomatische Löschung von nicht benutzten Qt-Elementen. Ich denke daher sind meine Objekte einfach verschwunden.
Ich habe trotzdem die Diskussion heir weiter verfolgt und finde das recht interessant. Aber eine Pointer-Gültigkeitsprüfung nach einem new-Operator werde ich mir sparen. Wenn meine Software den gesamten Ram in beschlag genommen hat, dann ist sowieso was schief gelaufen. ;) Das wäre meines Erachtens die einzige Möglichkeit warum new fehlschlagen kann. Die Software kommt sowieso dann später auf sehr leistungsstarken VR-Rechnern zum Einsatz die generell mehr als genug Ram besitzen.
Fehlererkennung und -behandlung ja, aber übertreiben muss man es ja nicht wie ich finde. :)
 
Zurück
Oben