Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Probleme mit for-Schleife


Gonozal_IX - Mo 03.12.07 12:20
Titel: Probleme mit for-Schleife
Hallo,

ich habe ein Problem mit einer for-Schleife, wo ich selbst nichtmehr weiterkomme. Wahrscheinlich ist es nur nen trivialer Fehler und ich seh den Wald vor lauter Bäumen nicht.

Also erstmal der Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function TForm8.holeForschungen : Tintegerarray;
var i,k : integer;
begin

for k := 0 to 2 do
  begin
  i := 0;
    while i<2 do
      i := i+1;
  end;
end;


Der Code selbst ist erstmal recht sinnfrei, allerdings ist er ausreichend für das Problem.
Beim Debuggen ist mir aufgefallen, dass die for-Schleife einmal durchlaufen wird und zwar mit dem Wert k=3....
und ich frage mich die ganze Zeit warum, wo sie doch eigentlich 3mal durchlaufen müsste mit k=0, k=1, k=2.

Vielleicht kann mir ja jemand einen Tip geben, so dass ich meinen Denkfehler verstehe.

Lieben Gruß,
Gono.

Moderiert von user profile iconTino: Code- durch Delphi-Tags ersetzt


F4n4T!C - Mo 03.12.07 12:30

mohoin^^ ... also erstmal^^ bei der for to schleife benutzt man glaube ich for k=0 to k=2

glaube so war das

dann würde ich dir raten...dass du im Formblatt (da wo du die ganzen buttons hinziehst) mal doppelt drauf klickst, und dem sagst "K:=0" damit er weiß, dass die variable ja null sein soll

versuch dass mal...lt hilft es ja^^


Gonozal_IX - Mo 03.12.07 12:35

user profile iconF4n4T!C hat folgendes geschrieben:
mohoin^^ ... also erstmal^^ bei der for to schleife benutzt man glaube ich for k=0 to k=2


Nein ;)


Zitat:
dann würde ich dir raten...dass du im Formblatt (da wo du die ganzen buttons hinziehst) mal doppelt drauf klickst, und dem sagst "K:=0" damit er weiß, dass die variable ja null sein soll

versuch dass mal...lt hilft es ja^^


Naja, dafür instanziiert man die Variable ja in der for-Schleife direkt. Aber selbst eine Instanziierung direkt vor der Schleife hilft nicht.
Im Formblatt direkt instanziieren mag ich sie nicht, weil ich sie dann global speichern müsste *pfui*
Die Variable wird auch nur lokal in der function benötigt, also gehört sie da auch nur hin.

Lieben Gruß
Gono.


Delete - Mo 03.12.07 12:45

Sollte eigentlich so funktionieren.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function TForm8.holeForschungen : Tintegerarray;
var i,k : integer;
begin
  for k := 0 to 2 do
  begin
    ShowMessage( IntToStr( k ) );   // Mach das mal dazu.
    i := 0;
    while i<2 do
      i := i+1;
  end;
end;


jackle32 - Mo 03.12.07 12:49

Hi,

ersmal die Syntax deiner Scheife stimmt, so wie du sie geschrieben hast. Hab sie mir auch grad 1:1 rauskopiert und getestet.

Es ist folgendes:

Wenn du k in der for Schleif nicht verwendest zählt Delphi k runter. Also in deinem Fall sind es ja drei Schritte also ist der Startwert 3 und die Schleif bricht ab wenn k Null ist. D.h. also er durchläuft die Schleif nicht nur einmal sondern schon dreimal wie gewünscht.

Wenn k jetzt benutzt wird, wenn auch nur zur Ausgabe (wie user profile iconNeurologic Scientist) vorschlägt zählt k wieder hoch.

Aber frag jetzt bitte nicht warum, könnte mir aber Vorstellen, das es was mit der Geschwindigkeit der Schleif zu tun hat, da man da auf das Nullflak im Prozessor reagieren kann und muss nicht die Abbruchbedingung jedes mal noch zusätzlcih abfragen. (Ist jetzt aber eine reine Vermutung).

Gruß Jack


Delete - Mo 03.12.07 12:53

user profile iconjackle32 hat folgendes geschrieben:
in der for Schleif nicht verwendest zählt Delphi k runter. [..] Warum das so ist hab ich keien Ahnung.

Weil der Compiler optimiert: for-Schleife läuft rückwärts [http://www.michael-puff.de/Artikel/2006/2006_07_13_for_backwards.php].


jackle32 - Mo 03.12.07 12:56

Na ja nach dem Bericht hab ich doch dann mit meiner Erklärung gar nicht so falsch gelegen *stolz guck*

Gruß Jack


Magic J - Mo 03.12.07 13:00

Hi

ist die auch schon aufgefallen, dass in deiner Function das RESULT fehlt?!

Sowas führt im Normalfall doch zum Fehler...

andere Fehler kann ich nun wirklich nicht endecken!

Gruß Jonas


Delete - Mo 03.12.07 13:03

user profile iconMagic J hat folgendes geschrieben:
ist die auch schon aufgefallen, dass in deiner Function das RESULT fehlt?!

Sowas führt im Normalfall doch zum Fehler...

Nein. Die Funktion bleibt einfach ohne definiertes Ergebnis. Ob Du einen Wert via Result zuweist oder nicht, ist in disem Fall wurscht ... denn

user profile iconGonozal_IX hat folgendes geschrieben:
Der Code selbst ist erstmal recht sinnfrei, allerdings ist er ausreichend für das Problem.


Aber rein algorhythmisch ist es ein Fehler.


Gonozal_IX - Mo 03.12.07 13:46

Hiho,

es gibt schon nen Result, ich hab den Code nur aufs Grundproblem gekürzt :)
Die Sache mit dem Runterzählen habe ich verstanden, das Problem ist nur, dass genauso verfahren wird, wenn ich k in der for-Schleife verwende!

.
.
.

Ok, ich hab das Problem gefunden. Es liegt dadran, dass Delphi anscheinend nicht erkennt, dass k in der Schleife verwendet wird.

Bisher hatte ich folgende Codezeile drin:


Delphi-Quelltext
1:
i := pos(suche[k],quellcode) + Length(suche[k]);                    


wobei suche ein array of string und quellcode ein string ist.
Nur mit dieser Codezeile zählt Delphi wirklich von 3 runter (obwohl suche[3] nicht definiert ist, darum auch der Fehler).

Ändert man dies ab in:

Delphi-Quelltext
1:
2:
i := k;
i := pos(suche[k],quellcode) + Length(suche[k]);


läuft alles prima.

Auf sowas muss man erstmal kommen. Danke für die Hilfe :)

Moderiert von user profile iconTino: Code- durch Delphi-Tags ersetzt


Delete - Mo 03.12.07 13:53

user profile iconGonozal_IX hat folgendes geschrieben:

Delphi-Quelltext
1:
2:
i := k;
i := pos(suche[k],quellcode) + Length(suche[k]);


läuft alles prima.


Das find ich zu irre. So sollte es nicht sein! In dem Moment, wo Du k als Index für den String und für das Array verwendest, MUSS er eigentlich hoch- anstatt runterzählen. Sont würdest Du zum Beispiel die Items einer ComboBox trotz vorwärtslaufender Schleife rückwärts auslesen. :gruebel:

Da stimmt doch was nicht. Hast Du mit ShowMessage( ) gearbeitet? Sollte rein als Überprüfung dienen.

//Edit: Liegt es vielleicht an einem Fehler im Komplett-Code? Eigentlich unwahrscheinlich, aber man weiß ja nie. Manchmal ...

//Edit2: Und denk daran, dass Du wahrscheinlich immer noch die While-Schleife im Code hast, die ebenfalls I verwendet und I auch einen neuen Wert zuweist. Ich kenne Deinen code nicht, daher nur Spekulation, dass Du da was verhaust.


jaenicke - Mo 03.12.07 14:05

Ich habe es gerade mit Delphi 2007 und einem Stringarray ausprobiert. k benutze ich ausschließlich als Index einmal. Es funktioniert wie es soll.
Könntest du vielleicht ein komplettes Beispielprojekt anhängen und schreiben welche Delphiversion du benutzt?

Bei dir funktioniert es ja zwar jetzt, aber zumindest ich fände es interessant, herauszufinden, warum das bei dir so ist. ;-)


Gonozal_IX - Mo 03.12.07 14:19

user profile iconjaenicke hat folgendes geschrieben:
Ich habe es gerade mit Delphi 2007 und einem Stringarray ausprobiert. k benutze ich ausschließlich als Index einmal. Es funktioniert wie es soll.
Könntest du vielleicht ein komplettes Beispielprojekt anhängen und schreiben welche Delphiversion du benutzt?


Hiho,

ich hab den Neurologic Scientist gerade ne PN geschrieben, aber da anscheinend mehr interesse besteht, hier auchnochmal 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:
function TForm8.holeForschungen : Tintegerarray;
var quellcode : string;
    suche : array[0..3of string;
    i,k : integer;
begin
quellcode := 'Teststring Eins(1), Zwei(2), Drei(3), Vier(4)';
suche[0] := 'Eins(';
suche[1] := 'Zwei(';
suche[2] := 'Drei(';
suche[3] := 'Vier(';

for k := 0 to 2 do
  begin
      i := pos(suche[k],quellcode) + Length(suche[k]);
      suche[k] := '';
      while quellcode[i] <> ')' do
        begin
         suche[k] := suche[k] + quellcode[i];
         i := i+1;
        end;

end;
result[0] := strtoint(suche[0]);
result[1] := strtoint(suche[1]);
result[2] := strtoint(suche[2]);
end;


Und jetzt das kuriose dazu: Er geht mit k = 3 in die Schleife (laut Debugger), der Debugger zeigt mir auch an suche[k] = 'Vier('

Wenn er dann aber zur Zeile


Quelltext
1:
suche[k] := '';                    

kommt, setzt er suche[0] auf '' (was ja im ersten durchlauf sein sollte).
Ich biete auch gerne ne Bilddoku dazu^^
Ich würds selber net glauben, wenn ichs net gesehen hätte oO

Zitat:

Bei dir funktioniert es ja zwar jetzt, aber zumindest ich fände es interessant, herauszufinden, warum das bei dir so ist. ;-)


Jaaa, ich auch! *g*

Gruß,
Gono.


jaenicke - Mo 03.12.07 14:57

Ich habe mir mal den generierten Assemblercode angesehen (einfaches Integerarray). Und jetzt ist mir klar, was passiert (sowas hatte ich mir auch schon fast gedacht ;-)):

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:
Unit11.pas.31: test[0] := 1;
0045AC7E C745F401000000   mov [ebp-$0c],$00000001

Unit11.pas.32: test[1] := 2;
0045AC85 C745F802000000   mov [ebp-$08],$00000002

Unit11.pas.33: test[2] := 3;
0045AC8C C745FC03000000   mov [ebp-$04],$00000003

// Bis hierhin wird lediglich an die Speicherstellen
// der Arrayeinträge der entsprechende Wert geschrieben.

Unit11.pas.34: j := 0;
0045AC93 33DB             xor ebx,ebx
// j wird in ebx berechnet, also wird ebx auf 0 gesetzt

Unit11.pas.35for i := 0 to 2 do
0045AC95 BA03000000       mov edx,$00000003
0045AC9A 8D45F4           lea eax,[ebp-$0c]
// in edx wird die Anzahl der Durchläufe geschrieben,
// in eax kommt die Adresse des ersten Eintrags des Arrays

Unit11.pas.36: j := j + test[i];
0045AC9D 0318             add ebx,[eax]
0045AC9F 83C004           add eax,$04
// zu ebx (j) wird der Wert an der Speicherstelle eax (des aktuellen Arrayeintrags) hinzugezählt
// die Speicheradresse in eax (der aktuelle Arrayeintrag) wird um 4 (die Größe eines Integerwerts) erhöht
// --> eax zeigt jetzt auf den nächsten Arrayeintrag

Unit11.pas.35for i := 0 to 2 do
0045ACA2 4A               dec edx
0045ACA3 75F8             jnz $0045ac9d
// Schleifenzähler verringern, zurück zum Anfang der Schleife
// springen, wenn edx noch nicht 0 ist
Was passiert ist also, dass der Zugriff auf das Array über die Adresse des aktuellen Eintrags geschieht, und dieser erhöht wird bei jedem Schleifendurchlauf. Damit ist der Wert des Schleifenzählers dafür unerheblich und dieser kann rückwärts laufen.
Der Delphicode komplett:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure TForm11.Button1Click(Sender: TObject);
var
  i, j: Integer;
  test: array[0..2of Integer;
begin
  test[0] := 1;
  test[1] := 2;
  test[2] := 3;
  j := 0;
  for i := 0 to 2 do
    j := j + test[i];
  ShowMessage(IntToStr(j));
end;


Gonozal_IX - Mo 03.12.07 15:03

Gut,

er lässt also das Array einfach anders laufen und darum läuft die Schleife rückwert, da optimiert.
Nur warum in Herrgottsnamen kriegt das der Debugger net gebacken, mir dass dann richtig anzuzeigen *gna*

Nagut, ich danke jedenfalls vielmals für die Hilfe :)

Gono.


Delete - Mo 03.12.07 15:11

Das hätte ich Dir via PN niemals so beantworten können. ;) Warum das so ist, kannst Du bei Borland nachfragen.


jaenicke - Mo 03.12.07 15:19

Der Grund ist vermutlich relativ einfach. Um das korrekt anzuzeigen, müsste der Debugger an der Stelle die Information haben, dass optimiert wurde (um die Werte aus den Registern korrekt umzurechnen). Ob optimiert werden kann oder nicht, ist aber sicher nicht ganz so einfach zu entscheiden. Im erstellten Binärcode steht davon auch nix mehr drin.
Also müsste der Debugger diese Informationen durch erneute Analyse des Quelltextes erneut gewinnen. Dadurch würde das Debuggen aber langsamer werden. Deshalb ist es wohl schon eine akzeptable Lösung, dass man davon ausgeht, dass ohne Optimierung kompiliert wurde. Denn wenn etwas schief geht, würde man ja als Entwickler normalerweise sowieso Debugausgaben einbauen (was dann eine Optimierung evtl. auch unmöglich macht), und die werden ja dann korrekt angezeigt.

Dass man wissen muss, dass Schleifen intern rückwärts laufen können, macht das anfangs etwas undurchsichtig, aber wenn man das weiß (zum Beispiel durch solch einen Thread hier ;-)), dann kann man an der Stelle auch damit umgehen.


Delete - Mo 03.12.07 15:52

Beim Debuggen sollte man sowieso die Optimierung abschalten.