[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

Small delphi debugger problem

Posted on 2008-11-16
15
Medium Priority
?
816 Views
Last Modified: 2013-11-23
I have a problem with the Delphi 5 debugger.

I have a break point in a for loop that goes from 0 to 100.

the debugger shows the loop counter as 101.

what is going on?

here is a screenshot:

http://img403.imageshack.us/my.php?image=screen2dl5.png
0
Comment
Question by:ThomasReimann
  • 6
  • 5
  • 4
15 Comments
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 22973808
sometimes the debugger messes up a for loop, dunno why
the thing i get in Delphi 7 is the loop is reversed, instead of 0 to 100 it goes 100 to 0
if you really want to see if the value is wrong,
the best way is to save a message with your for variable to a log file

i use a AddToLog proc to do this

in the loop just add a line to add to the log file

AddToLog(Format('I: %d, Color: %d', [I, Colors[I]));

My 2 cents about compiler directives:
They are very easy to use, but very dangerous.
Take this situation:
You test your app (with the compiler directive on) -> works ok
Then let somebody else test it -> works ok
Then put a version into production
If you have a nightly build system which recompiles an app
without the compiler directive, it will put apps into production that are not tested !
Compiler directives can cause totally different apps !




procedure AddToLog(Msg: string);
var
  LogFile: TextFile;
  TimeStr : string;
begin
  if Profiling then 
  begin
    if LastTime <= 1 then
      LastTime := Now;
    TimeStr := Now - LastTime;
    AssignFile(LogFile, ChangeFileExt(Application.Exe, '.log'));
    try
      Append(LogFile);
    except
      Rewrite(LogFile);
    end;
    Writeln(LogFile, Msg + '|' + FloatToStr(DateTimeToFloat(TimeStr)));
    CloseFile(LogFile);
  end;
end;
 
const Profiling: Boolean = False;
 
procedure ProfilingOn;
begin
  Profiling := True;
end;
 
procedure ProfilingOff;
begin
  Profiling := False;
end;

Open in new window

0
 

Author Comment

by:ThomasReimann
ID: 22974644
The loop is reserved here to.

It should go from 0 to 100 (no downto!!!)

the first 3 steps shoud be:
0 1 2

the debugged shows instead:

101 100 99

if it were

100 99 98 instead it would not worry me (it may be optimization, but {$O-} before the loop does not change anything.

what worries me is the value 101. how could the debugger mess up this?
0
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 22975302
dunno, but it happens only in debugging
the actual value it uses, seems ok
otherwise the for x := 0 to 100 wouldn't pass the loop more than once
it's like the watch is wrong

have you tried the logging option ?

what actually is your problem ?
besides the wrong debugger value.

does the app run fine otherwise ?
0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
LVL 26

Expert Comment

by:Russell Libby
ID: 22977002
What you are seeing is correct, and is caused by the code compiler optimizations for loop induction. You have asked for a loop to be executed 101 times, and that is what you will get.
eg:

for i:=0 to 100 do
begin
  beep;
end;

This equates to the following assembly code:

@@example:
MOV EBX, $00000065 // Load EBX with 101
CALL Beep                  // Loop body
DEC EBX                     // Decrement the register
JNZ @@example         // Jump if not zero

You will also find that if you utlilize the loop variable *within* the loop, then the produced code changes, and you will see the "expected" loop var value in the debugger, eg:

for i:=0 to 100 do
begin
  if (i = 20) then beep;
end;

The difference between the 2 is that in the first example you had no requirement to actually evaluate the loop inductor, thus allowing the compiler to generate more effecient code. The second example forces a CMP (compare) instruction to test for 100, which takes 2 clock cycles, vs the 1 for the JNZ.

Hope this helps,
Russell
0
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 22982859
Russel,
if i understand this correctly :

if you have a loop and you are using this loop variable inside the loop with a comparison
then the debugger will show the value of loop variable in the inverse direction of the loop ?
or is this just for the positive loops (low to high)

so if i want to write faster apps, i should allways write loops from high to low ?

0
 

Author Comment

by:ThomasReimann
ID: 22983069
@rllibby

I know the values are correct, this is only a debugging problem and I wanted an explanation for it.

If this is done by the code optimizer why does it still happen if I turn off optimization using {$O-} ?

also you say the asm code will change if I utilize the loop variable. but I do this already if you look at the code.

The line is

BColor[i] := myColor; // i beeing the loop counter, BColor[] an array
0
 
LVL 26

Accepted Solution

by:
Russell Libby earned 150 total points
ID: 22985384
What I am saying is that with code optimizations on, the compiler will produce optimized assembly code for the loop. In many cases, this means that the loop will be reversed as it is quicker to perform a JZ / JNZ instruction than a CMP instruction. Evaluating the local variable during debugging will not show expected values.

Geert, it does not mean you need to write your loops in reverse, as the compiler will produce the most optimized code path for you.

And its only *IF* the loop variable is induced in the loop routine that you can correctly evaluate (show in debug) the local var. An evaluate is the most simple way to induce the variable, and will always test (show in debug) correctly. Once induced in the routine, the generated code will  inc/dec the register in the direction that the loop is written, as running it in reverse would change the meaning of the code. Eg:

for i:=0 to 100 do
begin
  if (i = 20) then beep;
end;

running this from 100 down to zero would change the meaning of the code.

Now, indexers are a little bit different, and as ThomasReimann has pointed out, may not properly induce the loop variable. Take this simplified loop:

var
  Colors:  Array [0..100] of TColor;

procedure TForm1.Button1Click(Sender: TObject);
var  i:     Integer;
     c:     TColor;
begin

  for i:=0 to 100 do
  begin
     c:=Colors[i];
     c:=(c * 2) div 3;
     Colors[i]:=c;
  end;

end;

Now think about this: would it be more optimal to reload a register during each iteration using the address of Colors + Indexer * SizeOf(TColor), or would it be more optimal to load a register once and then inc / dec the register by the size of the indexed value? The compiler opts for the second, as it is more efficient. So in the example above the loop variable is not induced; almost (exactly) as if it had been written like:

type
  TColors  =  Array [0..MaxWord] of TColor;
  PColors  =  ^TColor;

var
  Colors:  Array [0..100] of TColor;

procedure TForm1.Button1Click(Sender: TObject);
var  lpColors:   PColors;
     i:          Integer;
     c:          TColor;
begin

  lpColors:=@Colors;
  for i:=0 to 100 do
  begin
     c:=lpColors^;
     c:=(c * 2) div 3;
     lpColors^:=c;
     Inc(lpColors);
  end;

end;

So when I mention loop variable induction, you have to understand that there are exceptions. This is also why I mentioned evaluation, as it is a sure way to induce.

Before we go on to the optimization flag, let me first say I am running Delphi 5 as well. I can't attest for other versions of Delphi, but this is how it works in D5. Setting the optimization flag {$O+/-} in either the on/off state also requires a second VERY IMPORTANT step. You must perform a full rebuild of the code in order to get the desired code (as the assembly code needs to be regenerated). With optimizations on, the following code runs the loop in reverse:

procedure TForm1.Button1Click(Sender: TObject);
var  i:          Integer;
begin

  for i:=0 to 100 do
  begin
     beep;
  end;

end;

Setting the optimization flag off and then hitting F9 will still show the loop var i as 101 (the first iteration). You need to perform a Project | Build Project, then run again. Now i tests as expected.

Russell

0
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 22985894
you really deserve the genius status for this clear explanation !
0
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 22985904
Btw,
Delphi 7 has the same issues
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 22990646
@Geert,
Thanks. Hopefully I was a little more clear the second time around. Its good information to have, but in practical use there isn't much application for it; short of interpreter/compiler design.

Russell
0
 

Author Comment

by:ThomasReimann
ID: 23001342
I have one remaining question.

My code must perform a

BColor[0] := myColor

and

BColor[100] := myColor

if the compiler choses a loop from 101 to 1 instead, does this mean that the code is changed to something like this?

BColor[i-1] := myColor
0
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 23001872
i don't think you need to change your code
if you would need to consider the optimisation and take the changes into account not much would work

the loop variable is only wrong in the watch

to check what assembly code the compiler created,
you could set a breakpoint at the beginning of your code
and then open the CPU window

then you may see how BColor[i] will be interpreted

0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 23004341
I agree with the above comment...

The compiler is going to produce the correct code, so your only issue is during debugging. Either turn off optimizations and rebuild / test. Or induce the loop variable with a simple if (i = {N}) do something, eg;

   if (i = 10) then OutputDebugString('i is 10');

If you really want to see what the resulting code is doing, then pop the CPU window and trace it that way. It will require that you understand assembly instructions though.

Russell


0
 

Author Comment

by:ThomasReimann
ID: 23011665
I did not aks If I need to change the code.

I've asked:

"does this mean that the code IS CHANGED to something like this?

BColor[i-1] := myColor"

I mean if the optimizers changes the code to this. Overwise the code can not work.

So apparently the optimization does not only reserve the loop, it changes the method the array is filled too.
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 23012524
If you perform the following:

>>
BColor[0] := myColor

and

BColor[100] := myColor
<<

then the indexer has no bearing on those 2 statements. If you have further questions then I suggest you open the CPU window to determine what the compiler is executing.


0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.
The viewer will learn how to use and create new code templates in NetBeans IDE 8.0 for Windows.
Suggested Courses
Course of the Month19 days, 2 hours left to enroll

834 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question