(C++) Direct3D und unerlaubter Speicherzugriff
Hopsel
- sonstiges
0 Der Martin0 mbr0 Der Martin0 mbr0 Der Martin0 Hopsel
Hi alle!
Ich versuche mich in der Programmierung mit Direct3D.
Der untenstehende Code wird anstandslos kompiliert und gelinkt.
Allerdings bekomme ich bei der Ausführung eine Fehlermeldung zu einem unerlaubten Speicherzugriff.
Wenn ich unten makierte Codezeile (der Übeltäter) auskommentiere, bekomme ich keinen unerlaubten Speicherzugriffsfehler mehr. Allerdings entsteht dann doch auch ein Memory-Leak, oder?
Ich kann beim besten Willen keinen Fehler sehen. Zumal der Code mit einem Buch erarbeitet wurde und nun nahezu vollständig in mein Programm eingeflossen ist.
// Auflisten alles auf dem System verfügbaren Direct3D-Adapter
#include <StdIO.h>
#include <D3D9.h>
int g_iNumAdapters;
PDIRECT3D9 g_pD3D = NULL;
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
char acAdapterInfo[1024];
char acTitle[64] = "Grafikkarte gefunden";
// Direct3D initialisieren
g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if(g_pD3D == NULL) {
// Fehler
MessageBox(NULL,"Fehler beim Erzeugen der D3D-Schnittstelle!","Fehler",MB_OK | MB_ICONEXCLAMATION);
return 1;
}
// Anzahl der Grafikkarten
g_iNumAdapters = g_pD3D->GetAdapterCount();
D3DADAPTER_IDENTIFIER9* g_pAdapters = new D3DADAPTER_IDENTIFIER9[g_iNumAdapters];
for(int iAdapter = 0; iAdapter < g_iNumAdapters; iAdapter++) {
if(FAILED(g_pD3D->GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters]))) {
// Keine Grafikkarte
MessageBox(NULL, "Keine Grafikkarte gefunden.", "Achtung!", MB_OK | MB_ICONEXCLAMATION);
// Direct3D freigeben
g_pD3D->Release();
delete[] g_pAdapters;
}
sprintf_s(acAdapterInfo,"Grafikkarte: %s",g_pAdapters[g_iNumAdapters].Description);
MessageBox(NULL, acAdapterInfo, acTitle, MB_OK | MB_ICONINFORMATION);
}
// Direct3D freigeben
g_pD3D->Release();
// ####################################
delete[] g_pAdapters; // DER ÜBELTÄTER
// ####################################
// ####################################
// ####################################
// ####################################
// ####################################
return 0;
}
MfG H☼psel
Moin,
Allerdings bekomme ich bei der Ausführung eine Fehlermeldung zu einem unerlaubten Speicherzugriff.
Wenn ich unten makierte Codezeile (der Übeltäter) auskommentiere, bekomme ich keinen unerlaubten Speicherzugriffsfehler mehr. Allerdings entsteht dann doch auch ein Memory-Leak, oder?
Ich werde den Eindruck nicht los, dass da in deinem Code ein logischer Fehler steckt. Du versuchst in der for-Schleife, die vorhandene(n) Grafikkarte(n) zu finden. Falls das in _einem_ Durchlauf fehlschlägt, gibst du innerhalb der Schleife sowohl das g_pD3D-Objekt wieder frei (g_pD3D->Release()) als auch das Array g_pAdapters.
Und nach dem Ende der Schleife tust du dasselbe nochmal?
Ohne die D3D-Funktionen genau zu kennen, würde ich zumindest sagen: Das ist verdächtig.
Abgesehen davon: Ist das korrekt, den delete-Operator noch mit Index-Klammern auszustatten? Anhand der Deklaration von g_pAdapters ist eh klar, dass es sich um ein Array handelt. Ich habe diese Klammern bei delete jedenfalls noch nie gesetzt; sie irritieren mich. Mag aber auch an meiner generell geringen Praxis mit OOP und C++ zusammenhängen - ich bevorzuge sonst "reines" C und malloc() und free() (bzw. deren Win32-API-Entsprechungen) zur Speicherverwaltung
Schönen Tag noch,
Martin
Hallo, Der Martin
Ich werde den Eindruck nicht los, dass da in deinem Code ein logischer Fehler steckt. Du versuchst in der for-Schleife, die vorhandene(n) Grafikkarte(n) zu finden. Falls das in _einem_ Durchlauf fehlschlägt, gibst du innerhalb der Schleife sowohl das g_pD3D-Objekt wieder frei (g_pD3D->Release()) als auch das Array g_pAdapters.
Und nach dem Ende der Schleife tust du dasselbe nochmal?
Ohne die D3D-Funktionen genau zu kennen, würde ich zumindest sagen: Das ist verdächtig.
Jau, war auch meine Idee - natuerlich warst du wieder mal etwas schneller ;-)
Allerdings sollte _dieses_ Problem nur im Fehlerfall auftreten und der OP hat nichts davon geschrieben, ob er die MessageBox sieht, oder nicht (gut, bin mir nicht sicher, ob man von der Abarbeitung des Programmcodes her eine Chance hat, sie zu sehen, oder ob der Fehler "schneller" ist ;-) )
Abgesehen davon: Ist das korrekt, den delete-Operator noch mit Index-Klammern auszustatten? Anhand der Deklaration von g_pAdapters ist eh klar, dass es sich um ein Array handelt. Ich habe diese Klammern bei delete jedenfalls noch nie gesetzt; sie irritieren mich. Mag aber auch an meiner generell geringen Praxis mit OOP und C++ zusammenhängen - ich bevorzuge sonst "reines" C und malloc() und free() (bzw. deren Win32-API-Entsprechungen) zur Speicherverwaltung
Also ich habe mal gelernt, dass man, wenn man Arrays alloziert immer die Klammern bei delete setzen sollte, auch wenn einige Compiler ohne auskommen. Ich meine auch, mich zu erinnern, dass mir dieses Problem schon mal auf die Füße gefallen ist (VC 6.0: mit [] funktionierte es, ohne schmierte mir das Programm ab) - allerdings ist das schon wieder ne Weile her und vielleicht trübt mich meine Erinnerung. Wie auch immer, ich setze die Klammern auf jeden Fall immer, wenn ich auch mit new ein Array angefordert habe.
mit freundlichem Gruß
mbr
Hi mbr!
Allerdings sollte _dieses_ Problem nur im Fehlerfall auftreten und der OP hat nichts davon geschrieben, ob er die MessageBox sieht, oder nicht (gut, bin mir nicht sicher, ob man von der Abarbeitung des Programmcodes her eine Chance hat, sie zu sehen, oder ob der Fehler "schneller" ist ;-) )
Der Fehler überholt die MessageBox nicht. Diese wird anstandslos angezeigt. Genauso, wie geplant. Allerdings kommt danach der hübsche Fehler. Es ist ein Debugfehler: "HEAP CORRUPTION DETECTED: [...] CRT detected that the application wrote to memory after end of heap buffer."
MfG H☼psel
Hallo H☼psel
Der Fehler überholt die MessageBox nicht. Diese wird anstandslos angezeigt. Genauso, wie geplant. Allerdings kommt danach der hübsche Fehler. Es ist ein Debugfehler: "HEAP CORRUPTION DETECTED: [...] CRT detected that the application wrote to memory after end of heap buffer."
Na dann ist doch alles klar (vorausgesetzt wir sprechen beide von der MessageBox direkt hinter dem Kommentar //keine Grafikkarte - aber ich fürchte, du meinst die MessageBox am Ende der Schlaife...). Wenn die Fehlermeldung angezeigt wird, wird das Zeiger Array freigegeben. Später wird dann nochmals verucht, das Array freizugeben. Falls du zwei oder mehr Adapter hast, müßte der Fehler sogar schon innerhalb der Schleife auftreten (Falls der Aufruf zweimal "scheitert" ;-) ). Ansonsten halt nach Beenden der Schleife. Übrigens hast du ganz richtig angemerkt, dass du beim Behandeln des ersten Fehlers (Initialisierung des Direct3D Objektes fehlgeschlagen) ein "return 1" stehen hast. Der zweite Fehlerfall innerhalb der Schleife (Keine Grafikkarte vorhanden) führt bei dir aber nicht zum Abbruch der Schleife. Allerdings solltest du die Schleife verlassen, nachdem du ein delete ausgeführt hast, denn was passiert, wenn die Funktion mit einem bereits freigegebenen Zeiger aufgerufen wird kannst du dir denken.
Also:
if(FAILED(g_pD3D->GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters]))) {
// Keine Grafikkarte
MessageBox(NULL, "Keine Grafikkarte gefunden.", "Achtung!", MB_OK | MB_ICONEXCLAMATION);
// Direct3D freigeben
g_pD3D->Release();
delete[] g_pAdapters;
//ergaenzung von mbr
g_pAdapters=NULL;
break;
}
mit freundlichem Gruß
mbr
P.S.: cool, scheint ja zu klappen mit der Quellcodeausgabe (SELF auf die Schulter klopf)
Hi mbr!
Ohjemine. Ich müsste eigentlich 50 Strafrunden nackt um den Block rennen.
Sehen wir uns mal die for-Schleife an:
for(
int iAdapter = 0
; iAdapter < g_iNumAdapters; iAdapter++) {
if(FAILED(g_pD3D->GetAdapterIdentifier(0, 0, > &g_pAdapters[g_iNumAdapters
]))) {
// Keine Grafikkarte
MessageBox(NULL, "Keine Grafikkarte gefunden.", "Achtung!", MB_OK | MB_ICONEXCLAMATION);
// Direct3D freigeben
g_pD3D->Release();
delete[] g_pAdapters;
}sprintf_s(acAdapterInfo,"Grafikkarte: %s",g_pAdapters[
g_iNumAdapters
].Description);MessageBox(NULL, acAdapterInfo, acTitle, MB_OK | MB_ICONINFORMATION);
}
Es geht um diese beiden Zeilen:
for(int iAdapter = 0; iAdapter < g_iNumAdapters; iAdapter++) {
und
if(FAILED(g_pD3D->GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters]))) {
Was fällt auf? Ich initialisiere die Schleifenvariable int iAdapters
mit 0, greife aber gar nicht darauf zu, sondern spreche den /die Adapter über &g_pAdapters[g_iNumAdapters]
. Ein einfacher Fehler, der komischer Weise nicht auffiel, weil die Ausgabe des Programms trotzdem richtig aussah.
Danke für eure Hilfe.
MfG H☼psel
Hi H☼psel
Ohjemine. Ich müsste eigentlich 50 Strafrunden nackt um den Block rennen.
Naja, immerhin ist es dir am Ende aufgefallen und nicht uns (Martin + mir). Aber dadurch wird natürlich alles klar.
Edit: Upps, oder doch nicht? Mir fällt gerade auf, dass ich in diesem Falle schon bei dem Aufruf
if(FAILED(g_pD3D->GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters]))) {
einen Fehler erwartet hätte. Du greifst doch hier auf ein Element zu, dass gerade ausserhalb des Arrays liegt - oder?
Wieso geht das ganze erst bei der Freigabe des Arrays schief? Das Array wird doch völlig korrekt alloziiert und sollte auch problemlos wieder freigegeben werden können. Na ja, ich glaub, ich hab immer noch nicht genug Ahnung von C(++) um wirklich alles zu verstehen...
Auf jeden Fall schön, dass du den Fehler gefunden hast. Falls ich wenigstens einen Denkanstoß geben konnte freut mich das natürlich auch. Viel Spaß beim weiteren Programmieren.
liebe Grüße
mbr
Hi mbr!
Edit: Upps, oder doch nicht? Mir fällt gerade auf, dass ich in diesem Falle schon bei dem Aufruf
if(FAILED(g_pD3D->GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters]))) {
einen Fehler erwartet hätte. Du greifst doch hier auf ein Element zu, dass gerade ausserhalb des Arrays liegt - oder?
Allerdings. Wer weiß, was der Compiler daraus macht...
MfG H☼psel
Hi Der!
Und nach dem Ende der Schleife tust du dasselbe nochmal?
Nein, tue ich nicht. Da bei einer vorherigen Freigabe das Programm mit return 1;
beendet wurde.
Abgesehen davon: Ist das korrekt, den delete-Operator noch mit Index-Klammern auszustatten?
Korrekt ja. Ich hab auch schon versucht, ohne Array zu arbeiten und einfach nur den ersten Adapter, den ich finde anzusprechen. Allerdings mit der gleichen Fehlermeldung.
MfG H☼psel
Hallo H☼psel
Kann es vielleicht sein, dass du g_pAdapters zweimal freigibst, weil der Code aus der Fehlerbehandlung ausgeführt wird? Falls nicht, hast du aber dennoch das Problem, dass du falls es jemals dazu kommen sollte(dass ein Fehler auftritt) der Zeiger zweimal freigegeben würde. Probier doch eventuell mal folgendes:
if(g_pAdapters) //Kurzschreibweise fuer if(g_pAdapters!=NULL)
{
// ####################################
delete[] g_pAdapters; // DER ÜBELTÄTER
// ####################################
// ####################################
// ####################################
// ####################################
// ####################################
}
mit freundlichem Gruß
mbr
Hallo,
if(g_pAdapters) //Kurzschreibweise fuer if(g_pAdapters!=NULL)
das mit der Kurzschreibweise ist richtig - aber der Grundgedanke ein Trugschluss! Ich habe eben mal ein bisschen recherchiert und bin zu folgendem Ergebnis gekommen:
1. Der delete-Operator akzeptiert auch klaglos einen NULL-pointer. Das führt nicht zu einer Fehlermeldung; es passiert einfach bloß nichts.
2. Beim erfolgreichen delete wird der Zeiger _nicht_ auf NULL gesetzt, sondern behält seinen aktuellen, nun nicht mehr gültigen Wert. Deshalb ist die Abfrage, die du vorschlägst, sinnlos (Meine einzige Fundstelle zu _diesem_ Thema: http://www.linuxdocs.org/HOWTOs/C++Programming-HOWTO-10.html).
Fazit:
Wenn diese Zeile
delete[] g_pAdapters; // DER ÜBELTÄTER
einen Fehler auslöst, muss g_pAdapters einen von NULL verschiedenen Wert haben, der aber nicht auf das erwartete Array verweist!
Ciao,
Martin
Hallo,
du hast natürlich recht. Ich hatte auch vergessen, dass ich normalerweise (Wenn ich delete in if-Bedingungen einsetze) sowas schreibe:
delete pZeiger;
pZeiger=NULL;
Wobei ich auch eigentlich - ohne mich jetzt entschuldigen zu wollen - gar nicht so sehr daran gedacht habe, dass es an so einem doppelten delete liegen könnte, sondern eher, dass der Zeiger irgendwie auf einen "falschen" Speicherbereich zeigt. Wie auch immer: ich weiss ja gar nicht, was die Funktion
GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters])
macht. Vielleicht wird ja auch dort der Zeiger schon implizit wieder freigegeben?
Ich experimentiere hier übrigens gerade mit libharu (freie PDF Bibliothek für C++) herum und da gibt es in den Beispielen haufenweise Zeilen wie
/* Add font with PdfStandardEncoding and name it "StandardEncoding"*/
doc->AddType1Font(fd2, "StandardEncoding", new PdfStandardEncoding());
Und natürlich weit und breit kein delete zu sehen. Wie sollte es auch? Es wird ja ein neues Objekt erzeugt, das eigentlich keinen Namen hat (höchstens einen internen für den Compiler).
Aber: Memory-leaks scheint es auch nicht zu geben (zumindest, wenn ich Visual Studio trauen darf ;-) )
Vielen Dank übrigens für den Hinweis mit dem NULL-Zeiger und delete. Das wußte ich bisher noch nicht. Ist aber auch logisch (wie ich _jetzt_ gelesen habe), weil nämlich new im Fehlerfall (Wann passiert das schon?) NULL als Ergebnis zurückliefert.
Liebe Grüße
mbr
Nachtrag: habe mir gerade mal aus reiner Neugierde den Destruktor von PdfStandardEncoding angeschaut. weil ich dachte, da könnte evtl. sinngemäß sowas stehen:
delete this;
this=NULL;
(auch wenn ich nicht sicher bin, ob das Sinn machen würde - oder vielleicht sogar gefährlich ist). Aber dem ist nicht so. Es gibt lediglich ein paar interen Aufräumarbeiten.
Mahlzeit!
du hast natürlich recht.
Das passiert mir gelegentlich. ;-)
GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters])
Vielleicht wird ja auch dort der Zeiger schon implizit wieder freigegeben?
Nein - oder wenn, dann zumindest nicht korrekt.
Denn hier wird ja als Parameter ein Zeiger auf _ein ausgewähltes_ Array-Element übergeben, während der "Originalzeiger" dabei nicht verändert wird. Merke: Auch wenn ein '&' vorne dransteht, wird hier nicht der Zeiger "by reference" übergeben!
Vielen Dank übrigens für den Hinweis mit dem NULL-Zeiger und delete. Das wußte ich bisher noch nicht.
Gern geschehen - ich wusste es auch noch nicht. Aber da ich mit C++ nicht so viel am Hut habe (benutze lieber C ohne ++), ist das auch nicht schlimm.
Schönes Wochenende schonmal,
Martin
Hi mbr!
GetAdapterIdentifier(0, 0, &g_pAdapters[g_iNumAdapters])
Diese Funktion füllt mein Array mit Werten der verschiedenen Adapter des Systems. Natürlich gibt sie den Speicherbereich nicht frei. Ich möchte die Werte ja noch abfragen.
MfG H☼psel