Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Container Klasse Speicherverletzung


Dude566 - Mo 16.11.09 20:57
Titel: Container Klasse Speicherverletzung
Also wir haben in der Schule jetzt mal mit OOP angefangen, ich komme nicht so ganz mit den Unterrichtsmethoden zurecht (Chaos).

Ich hab eine Container Klasse und eine Klasse TRing, doch ich bekomme eine Zugriffsverletzung wenn ich TRing in TContainer erstellen möchte.

TContainer

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
unit uTContainer;

interface

uses uTRing, Graphics;

type TContainer = class(TObject)
     private
       Item : array [1..5of TRing;   // Instanzen(Objekte) von Klasse TRing
     public
       constructor Create;
       destructor Free;
       procedure Init;

     end;

implementation

// Constructor
constructor TContainer.Create;
begin
     inherited Create;
     Init;
end;

// Destructor
destructor TContainer.Free;
var
   i : integer;
begin
     for i := 0 to 4 do
         Item[i].Free;
end;

procedure TContainer.Init;
var
   i : integer;
begin
     for i := 0 to 4 do
         Item[i] := TRing.Create;
end;

end.


TRing

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
unit uTRing;

interface

uses Graphics;

type TRing = class(TObject)
     private
       stift : integer;         // Variable für Stiftdicke
       farbe : TColor;          // Variable für Stiftfarbe
       x1,y1,x2,y2 : integer;   // Variablen für Ringposition
     public
       constructor Create;
       destructor Free;
       procedure Init;
       procedure SetStaerke(dicke : integer);  // Stiftdicke setzen
       function  GetStaerke : integer;         // Stiftdicke ausgeben
       procedure SetFarbe (stiftfarbe : TColor);   // Stiftfarbe setzen
       function  GetFarbe : TColor;                // Stiftfarbe ausgeben
       procedure SetPosition(ObenLinksX,ObenLinksY,UntenRechtsX,UntenRechtsY : integer);
       procedure Zeichnen (myCanvas : TCanvas);
     end;

implementation

procedure TRing.SetStaerke(dicke : integer);
begin
     stift := dicke;
end;

function TRing.GetStaerke : integer;
begin
     result := stift;
end;

procedure TRing.SetFarbe (stiftfarbe : TColor);
begin
     farbe := stiftfarbe;
end;

function TRing.GetFarbe : TColor;
begin
     result := stift;
end;

procedure TRing.SetPosition(ObenLinksX,ObenLinksY,UntenRechtsX,UntenRechtsY : integer);
begin
     x1 := ObenLinksX;
     y1 := ObenLinksY;
     x2 := UntenRechtsX;
     y2 := UntenRechtsY;
end;

procedure TRing.Zeichnen (myCanvas : TCanvas);
begin
     myCanvas.Pen.Color := GetFarbe;
     myCanvas.Pen.Width := GetStaerke;
     myCanvas.Ellipse(x1,y1,x2,y2);
end;

constructor TRing.Create;
begin
     inherited Create;
     Init;
end;

destructor TRing.Free;
begin
end;

procedure TRing.Init;
begin
     SetStaerke(3);
     SetFarbe(clLime);
     SetPosition(50,50,150,150);
end;

end.


Beim Highlight tritt der Fehler auf, ich stehe auf dem Schlauch und weis nicht weiter. :nixweiss:

Gruß Dude566


Mitmischer 1703 - Mo 16.11.09 21:30

Item ist ein Array von 1..5, aber in der for-Schleife greifst du auf 0..4 zu - und wenn du auf 0 zugreifst, knallt's :)


Dude566 - Mo 16.11.09 22:14

Ja aber der Index geht doch bei 0 los. :nixweiss:

Und selbst wenn ich es korrigiere wie du sagst tritt der fehler immer noch auf.
Auch eine Zugriffsverletzung.


Niko S. - Di 17.11.09 07:09

Sicher dass du bei der for schleife jetzt 1 to 5 hast?
Außerdem musst du bei "Free" nicht auch zum schluss den vorherigen Code übernehmen "inhertied" ?
(Schlimm wenn einem die richtigen Worte fehlen)


BenBE - Di 17.11.09 12:00

1. Ein Destructor heißt Destroy
2. Free Überschreibt man nicht
3. Maximal Destroy überschreiben, dann aber nicht vergessen, inherited aufzurufen (WICHTIG!)
4. Deine Indizes hauen nicht hin (Deklaration 1..5, Nutzung in der Schleife 0..4)
5. Warum lagerst Du das Zeug in eine zusätzliche Methode Init aus?
6. Ggf. eine etwas regelmäßiger Code-Formatierung angewöhnen.


Delete - Di 17.11.09 13:02

Ich habe mal ein Tutorial dazu geschrieben, eventuell hilft es: http://www.michael-puff.de/Developer/Delphi/Tutorials/


Dude566 - Di 17.11.09 15:06

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
1. Ein Destructor heißt Destroy
2. Free Überschreibt man nicht
3. Maximal Destroy überschreiben, dann aber nicht vergessen, inherited aufzurufen (WICHTIG!)


Unsere Lehrerin hat uns das gesagt, aber die hat meistens eh keine Ahnung warum sie das so und so macht. :roll:

Zum Rest: Ich probiere dann mal den Fehler zu beheben, werde mir auch user profile iconLuckies Tutorial mal reinziehen, und mich bei Fragen dann wieder hier melden.

Edit: Warum im Destructor das inherited aufrufen? Ich begfreife nicht so recht was das macht, unsere Lehrerin konnte es auch nicht erklären.


Nersgatt - Di 17.11.09 15:36

Du überschreibst ja den Destruktor von TObject, weil Du von TObject abgeleitet hast. Mit dem Aufruf von Inherited ruft Du dann den Destructor der Basisklasse (hier TObject) auf. So wird der Code, der im Destructor von TObject steht, auch ausgeführt. Ohne Inherited unterschlägst Du ihn. Das kann gefährliche Nebeneffekte haben.


Critter - Di 17.11.09 15:53

Hi,

hier [http://www.delphi-forum.de/viewtopic.php?p=580740#580740] hatte ich kürzlich mal ein kleines Beispiel zu inherited gepostet, vielleicht hilft das beim Verständnis.

critter


Mitmischer 1703 - Di 17.11.09 16:08

user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Ja aber der Index geht doch bei 0 los. :nixweiss:


Nein ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type TContainer = class(TObject)
     private
       Item : array [1..5of TRing;   // Instanzen(Objekte) von Klasse TRing
     public
       constructor Create;
       destructor Free;
       procedure Init;

     end;


Was genau hast du denn verändert :) ?


Dude566 - Di 17.11.09 16:47

user profile iconCritter hat folgendes geschrieben Zum zitierten Posting springen:
Hi,

[url]hier[/url] hatte ich kürzlich mal ein kleines Beispiel zu inherited gepostet, vielleicht hilft das beim Verständnis.

critter


Ich glaube du hast bei der Angabe des Links einen Fehler gemacht. ;)


HelgeLange - Di 17.11.09 16:56

Du rufst auch ein inherited beim Create auf, obwohl du es nicht überschrieben hast und ich glaube, TObject bietet auch kein Create an.


Critter - Di 17.11.09 17:04

Hi,
user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Ich glaube du hast bei der Angabe des Links einen Fehler gemacht. ;)


Ja das habe ich wohl :(. Habe den Link oben Repariert, aber zur Sicherheit gibt es den jetzt gleich noch einmal ;): http://www.delphi-forum.de/viewtopic.php?p=580740#580740

critter


Dude566 - Di 17.11.09 21:02

user profile iconMitmischer 1703 hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Ja aber der Index geht doch bei 0 los. :nixweiss:


Nein ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type TContainer = class(TObject)
     private
       Item : array [1..5of TRing;   // Instanzen(Objekte) von Klasse TRing
     public
       constructor Create;
       destructor Free;
       procedure Init;

     end;


Was genau hast du denn verändert :) ?


Also ich habe in TContainer jetzt folgendes:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
unit uTContainer;

interface

uses uTRing, Graphics;

type TContainer = class(TObject)
     private
       Item : array [1..5of TRing;   // Instanzen(Objekte) von Klasse TRing
     public
       constructor Create;
       destructor Destroy;
end;

implementation

// Constructor
constructor TContainer.Create;
var
   i : integer;
begin
     for i := 1 to 5 do
         Item[i] := TRing.Create;
end;

// Destructor
destructor TContainer.Destroy;
var
   i : integer;
begin
     inherited Create;
     for i := 1 to 5 do
         Item[i].Destroy;
end;

end.


Und bekomme immer noch die Zugriffsverletzung, ich dachte ich hätte es jetzt so wie ihr sagt angepasst. :nixweiss:


Xentar - Di 17.11.09 21:26

1. Verschieb das inherited create; auch ins Create.. im Destroy hat das nix zu suchen.
2. Nicht Item[i].Destroy aufrufen, sondern .Free


ersteres sollte dir bei deinem Problem weiterhelfen.


Dude566 - Di 17.11.09 21:29

user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
1. Verschieb das inherited create; auch ins Create.. im Destroy hat das nix zu suchen.
2. Nicht Item[i].Destroy aufrufen, sondern .Free


ersteres sollte dir bei deinem Problem weiterhelfen.


Nein hat es leider nicht, und ich habe Free in Destroy auf anraten einer deiner Vorredner geändert.


HelgeLange - Di 17.11.09 21:48

Man ruft kein destroy auf, nimm free

ausserdem lösch das "inherited Create" complett raus, TObject hat kein create zum überschreiben und im Destroy hat das schon garnichts zu suchen.


Xentar - Di 17.11.09 21:54

user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Nein hat es leider nicht

Maa, dann schmeiß halt den Debugger an ;)
Setz nen Haltepunkt, geh Schritt für Schritt durch, und guck, wo der sich aufhängt.. vielleicht hast auch noch nen Fehler in der Ring Klasse, oder so?


jaenicke - Mi 18.11.09 11:11

user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
vielleicht hast auch noch nen Fehler in der Ring Klasse, oder so?
Zumindest in der geposteten Version ist das auch so, wie sofort zu sehen ist:
user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
       destructor Free;
...

destructor TRing.Free;
begin
end;
Was passiert jetzt? Einerseits ist Free jetzt nicht mehr das originale Free, andererseits wird zusätzlich auch noch der geerbte Destruktor nicht mehr aufgerufen.
Kurz gesagt: alles kaputt was kaputtzumachen geht. :mrgreen:

Also: destructor Destroy; und dort inherited; nicht vergessen, fertig...


Martok - Mi 18.11.09 11:12

user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
Maa, dann schmeiß halt den Debugger an ;)

Tut mir leid jetzt hier quasi-offtopic dazwischenzuhauen.

Debugging lernt man in der Schule nicht. Zumindest kenne ich keinen wo das gemacht worden wäre. Warum das so ist, keine Ahnung. Entspricht aber voll und ganz dem im deutschen Schulsystem verankerten "auswendiglernen statt verstehen". Selber debuggen würde ja eine Fähigkeit schulen, wo kommen wir denn da hin. Da lernt man lieber Code auswendig und schreibt den dann in irgendwelchen Tests hin.

Wäre es anders, würden diesem Forum locker 50% der Fragen "erspart" bleiben.


jaenicke - Mi 18.11.09 11:14

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Debugging lernt man in der Schule nicht.
Da hast du sicher Recht, dafür gibt es aber genug Tutorials wie dieses:
http://www.delphi-treff.de/delphi-ide/debugger/


Dude566 - Mi 18.11.09 18:21

Könnt ihr euch einigen? user profile iconHelgeLange und user profile iconjaenicke sagen beide was anderes. Ich probiere noch mal rum.

Aber wie user profile iconMartok schon sagte, lernt man nie Debugging, die Lehrer selbst könnens ja meistens nicht mal.

Edit: Habe mir jetzt mal schnell das Tutorial von meinem Vorposter angeschaut und bin den Text mal mit F7 Zeilenweise durchgegangen.

An einer Stelle komme ich dann nicht weiter und es kommt die Zugriffsverletzung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
// Constructor
constructor TContainer.Create;
var
   i : integer;
begin
     for i := 1 to 5 do
         Item[i] := TRing.Create;
end;


Xentar - Mi 18.11.09 18:44

user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Könnt ihr euch einigen? user profile iconHelgeLange und user profile iconjaenicke sagen beide was anderes. Ich probiere noch mal rum.

Der destructor heißt Destroy, ja. Diesen ruft man aber nicht direkt auf, sondern benutzt .Free

user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Aber wie user profile iconMartok schon sagte, lernt man nie Debugging, die Lehrer selbst könnens ja meistens nicht mal.

Das ist traurig.. wie suchen die selber denn dann Fehler? Fragen die auch im Forum nach? :D

Könntest du bitte nochmal den aktuellen Code von beiden Klassen posten?


Dude566 - Mi 18.11.09 18:50

Ja das ist traurig aber wirklich wahr, deshalb finden wir oft Fehler erst spät oder garnicht. :roll: :autsch:

Hier der komplette Code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
unit uTContainer;

interface

uses uTRing, Graphics;

type TContainer = class(TObject)
     private
       Item : array [1..5of TRing;   // Instanzen(Objekte) von Klasse TRing
     public
       constructor Create;
       destructor Destroy;
end;

implementation

// Constructor
constructor TContainer.Create;
var
   i : integer;
begin
     for i := 1 to 5 do
         Item[i] := TRing.Create;
end;

// Destructor
destructor TContainer.Destroy;
var
   i : integer;
begin
     for i := 1 to 5 do
         Item[i].Destroy;
end;

end.



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
unit uTRing;

interface

uses Graphics;

type TRing = class(TObject)
     private
       stift : integer;         // Variable für Stiftdicke
       farbe : TColor;          // Variable für Stiftfarbe
       x1,y1,x2,y2 : integer;   // Variablen für Ringposition
     public
       constructor Create;
       destructor Destroy;
       procedure Init;
       procedure SetStaerke(dicke : integer);  // Stiftdicke setzen
       function  GetStaerke : integer;         // Stiftdicke ausgeben
       procedure SetFarbe (stiftfarbe : TColor);   // Stiftfarbe setzen
       function  GetFarbe : TColor;                // Stiftfarbe ausgeben
       procedure SetPosition(ObenLinksX,ObenLinksY,UntenRechtsX,UntenRechtsY : integer);
       procedure Zeichnen (myCanvas : TCanvas);
     end;

implementation

procedure TRing.SetStaerke(dicke : integer);
begin
     stift := dicke;
end;

function TRing.GetStaerke : integer;
begin
     result := stift;
end;

procedure TRing.SetFarbe (stiftfarbe : TColor);
begin
     farbe := stiftfarbe;
end;

function TRing.GetFarbe : TColor;
begin
     result := stift;
end;

procedure TRing.SetPosition(ObenLinksX,ObenLinksY,UntenRechtsX,UntenRechtsY : integer);
begin
     x1 := ObenLinksX;
     y1 := ObenLinksY;
     x2 := UntenRechtsX;
     y2 := UntenRechtsY;
end;

procedure TRing.Zeichnen (myCanvas : TCanvas);
begin
     myCanvas.Pen.Color := GetFarbe;
     myCanvas.Pen.Width := GetStaerke;
     myCanvas.Ellipse(x1,y1,x2,y2);
end;

constructor TRing.Create;
begin
     Init;
end;

destructor TRing.Destroy;
begin
     inherited;
end;

procedure TRing.Init;
begin
     SetStaerke(3);
     SetFarbe(clLime);
     SetPosition(50,50,150,150);
end;

end.


Xentar - Mi 18.11.09 20:31

Tut bei mir, abgesehen von zwei Compilerwarnungen, einwandfrei.
Wie erzeugst du das ganze?
In welcher Zeile bleibt Delpi stehen, wenn der Fehler auftritt?


BenBE - Mi 18.11.09 23:03

Zur Erklärung ggf. mal folgende (konventionellen) Regeln:
1. In einem Construktor sollte inherited Create; die erste Zeile nach dem Begin sein. Dies ändert sich nur, wenn für diesen Aufruf lokale Variablen oder andere Dinge vorzubereiten sind. Bis zu diesem Zeitpunkt sind Zugriffe auf Instanz-Variablen aber absolutes Tabu!

2. In einem Destructor mit Namen Destroy ist die Letzte Zeile vor dem End der Aufruf der Vorfahrklasse. Diese Konvention ist hier zwingend, da es danach keine weiteren Aufgaben mehr gibt.

3. (wurde aber schon gesagt): Der Aufruf von Destroy erfolgt indirekt über die Methode Free.

Anmerkungen:
- Zum Überschreiben des Konstruktors bei TObject benötigt man das Methoden-Flag reintroduce;. Siehe Hilfe für mehr Informationen dazu.
- Construcotr und Destructor erzeugen intern spezielle Aufruf-Konventionen, bei denen neben den eigentlichen Argumenten auch weitere Daten übergeben. Diese regeln u.a. ob die Methode NewInstance aufgerufen wird, die den Speicher alloziiert. Eine genaue Erklärung führt hier zu weit.


Dude566 - Do 19.11.09 00:22

user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
Tut bei mir, abgesehen von zwei Compilerwarnungen, einwandfrei.
Wie erzeugst du das ganze?
In welcher Zeile bleibt Delpi stehen, wenn der Fehler auftritt?



Delphi-Quelltext
1:
2:
 for i := 1 to 5 do
Item[i] := TRing.Create;


Beim Create bricht mein Programm dann ab...


jaenicke - Do 19.11.09 01:21

Ich rate einmal (da du den Quelltext davon nicht postest):
Du erzeugst dein TContainer so...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var
  Container: TContainer;
begin
  Container.Create;
  ...
  // oder:
  Container := Container.Create;
Also eben nicht das Create der Klasse sondern der (nicht existenten) Instanz der Klasse. Da fehlt also das T vermute ich.


Dude566 - Do 19.11.09 17:15

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var
  Container: TContainer;
begin
  Container.Create;
  ...
  // oder:
  Container := Container.Create;


Voll ins Schwarze :oops: :oops: , ich habe in meiner main.pas die erste Variante die du gepostet hast geschrieben, also wurde garkeine Instanz des Objekts TContainer erstellt. :autsch:
Sowas blödes! :autsch:

Aber warum wurde mir dann nicht schon dort ein Fehler angezeigt, ich meine der Compiler müsste das doch erkennen, wenn ich dort einfach komplett falsch ein Create für das Objekt aufrufe. :?


Mitmischer 1703 - Do 19.11.09 19:16

Ist halt ein Laufzeitfehler :zwinker:


jaenicke - Fr 20.11.09 00:50

user profile iconDude566 hat folgendes geschrieben Zum zitierten Posting springen:
Aber warum wurde mir dann nicht schon dort ein Fehler angezeigt, ich meine der Compiler müsste das doch erkennen, wenn ich dort einfach komplett falsch ein Create für das Objekt aufrufe. :?
Sofern es eine lokale Variable ist, sollte eine Warnung kommen, dass die Variable evtl. nicht initialisiert ist. Wenn es aber eine globale ist, hat der Compiler keinerlei Möglichkeit zu erkennen, ob da nicht irgendwoher schon ein Objekt drin ist. ;-)