Jump to content
Allan Olesen

'for i:= 0 to 4' does 6 loops when not in Debug mode

Recommended Posts

I have taken over some old Delphi code and have run into a strange bug where a for loop behaves differently in Debug and Release configuration. I assume the code creates a memory problem, where a variable is overwritten or released too early, but I am in over my head and could use some hints.

 

The code is shown below in a simplified version. A lot goes on in that procedure, including a try without except, so I have cut away a lot of code and variables, as illustrated by my bogus "doStuff" lines.

 

Some notes:

  • The TOutputValues type is a custom class, based on TObject.
  • The OperationPoints object is a TStrings object containing 5 TOutputValues objects.
  • The MessageDlg lines was inserted by me, because the error only shows in Release mode, not in Debug mode, so I can't use Watch.
  • I have no idea why there is both an i and an iii variable, which are manually kept in sync. There is no manipulation of i or iii in in the code, which I have cut away in the example, so they should always be in sync. But I think this is a red herring.

 

When I run this code, the output of the message dialogs is exactly as expected during the 5 first loops. The value of imax stays at 4, and i and iii counts up from 0 to 4.

 

But then it starts a 6th loop with i=5, iii=5 and imax=4, which doesn't make sense. This loop of course fails because OperationPoints.Objects[iii] is out of bounds.

 

As I said, I have a feeling that this must be a memory handling error somewhere in the code, but I do not have much experience with compiled languages and memory debugging. My uneducated guess would be that the for loop does not test the value of i against the memory address where imax is stored, but instead stores a copy of imax at another memory address, and this address is somehow overwritten with another value during code execution. Does this sound right? Any ideas of which kind of bad code could cause such behaviour?

 

procedure MyType.CalcStuff;

var
  i, imax, iii                          :Integer;
  AOperationPoint                       :TOutputValues;
begin

  doStuff

  imax := OperationPoints.Count-1;
  iii := 0;
  for i := 0 to imax do
  begin
    MessageDlg('Dlg1 i iii imax ' + inttostr(i) + ' ' + inttostr(iii) + '/' + inttostr(imax) ,mtWarning,[mbOk], 0, mbOk);
    AOperationPoint := TOutputValues(OperationPoints.Objects[iii]);

	doStuff	

    iii := iii + 1;
    MessageDlg('Dlg3 i iii imax ' + inttostr(i) + ' ' + inttostr(iii) + '/' + inttostr(imax) ,mtWarning,[mbOk], 0, mbOk);
  end;

  doStuff

end;

 

Other information:

  • Embarcadero® Delphi 11 Version 28.0.47991.2819 
  • Windows 10, 64 bit
Edited by Allan Olesen

Share this post


Link to post
8 minutes ago, Allan Olesen said:

I assume the code creates a memory problem

Well, does it? To me, it sounds like the compiler is doing some optimization, and you're confused by what the debugger is temporarily showing, while stepping through the loop.

Share this post


Link to post
3 minutes ago, Der schöne Günther said:

Well, does it? To me, it sounds like the compiler is doing some optimization, and you're confused by what the debugger is temporarily showing, while stepping through the loop.

I am not using the debugger. I can't, because the code works when I run it in Debug configuration. So I am debugging with message dialogs in Release configuration. And even some of those message dialogs (not those shown here) will cause the code to work. So this is like one of the physics experiments where testing the outcome will change the outcome.

 

But I assume that no optimization should cause a 'for i := 0 to 4' loop to run 6 times?

Share this post


Link to post

> 'for i:= 0 to 4' does 6 loops when not in Debug mode

 

No it doesn't. 

Edited by David Heffernan

Share this post


Link to post
33 minutes ago, Uwe Raabe said:

Does it work when you omit doStuff?

Yes, if I omit the second doStuff - the one inside the for loop - the for loop will exit cleanly with only 5 passes.

 

I know this is not a lot of info to go on, so I am right now trying to narrow it down more, but it is difficult because everything I do to get debug information through message dialogs will change the behaviour.

 

For example, right now with some additional testing added and some message dialogs commented out, imax is somehow being reset to 0, but the loop still runs out of bounds. If I comment in those dialogs again, so I can track the behaviour, imax will not get reset. 

Share this post


Link to post
33 minutes ago, Allan Olesen said:

if I omit the second doStuff - the one inside the for loop - the for loop will exit cleanly with only 5 passes.

Then the error is most likely located inside that code.

  • Like 1

Share this post


Link to post
1 hour ago, Uwe Raabe said:

Then the error is most likely located inside that code.

Yes, finally found it, taking a deep dive into functions called from doStuff. One of these function could be boiled down to:

 

MyVar: array[0..6] of double;

For i:=1 to 8 do Myvar[i]:=0;

So I guess that those extra (and offset) zeroes spilled into memory space, which was used to control my other loop.

 

Thanks.

Share this post


Link to post

How would this code behave differently between Debug and Release? Do both compile?

Edited by PeaShooter_OMO

Share this post


Link to post
10 minutes ago, Allan Olesen said:

Yes, finally found it, taking a deep dive into functions called from doStuff. One of these function could be boiled down to:

 


MyVar: array[0..6] of double;

For i:=1 to 8 do Myvar[i]:=0;

So I guess that those extra (and offset) zeroes spilled into memory space, which was used to control my other loop.

 

Thanks.

Enable range checking, at least in debug builds

  • Like 3

Share this post


Link to post
5 minutes ago, PeaShooter_OMO said:

How would this code behave differently between Debug and Release? Do both compile?

My guess is different memory layout in debug and release mode. That's why the code inserted to debug changed the behaviour of the bug.

Edited by Lajos Juhász
  • Like 1

Share this post


Link to post
1 minute ago, PeaShooter_OMO said:

How would this code behave differently between Debug and Release? Do both compile?

Both compiled. Both would of course spill into memory (when range checks are disabled, at least), but I guess that the debug version of the .exe used memory differently, so the spill damaged something else, which did not interfere with the loop in my first post.

 

 

Share this post


Link to post
5 minutes ago, David Heffernan said:

Enable range checking, at least in debug builds

Thanks. After making my discovery, I have been stumbling around in Build Configuration settings for this project and other projects, investigating exactly that.

 

Both the range check and the overflow check were disabled in the Debug build configuration.

 

I am now wondering if this is a remnant of an old default behavior in RAD Studio? If yes, are there other "bad" defaults, that I should be aware of?

 

(It is not a current default. If I start a new project from scratch, both are enabled, as I would have expected.)

Share this post


Link to post

I'd think your time would be better invested changing code like this:

 

for i := 1 to 8 do Myvar[i] := 0;

 

to this:

 

for i := Low(Myvar) to High(Myvar) do Myvar[i] := 0;

 

when the target is an array type. Or use an iterator if possible, but it's not possible here.

 

Share this post


Link to post
8 minutes ago, David Schwartz said:

I'd think your time would be better invested changing code like this:

 

I am way ahead of you.

 

But I need to find those gems in the code I have taken over, before I can change them.

 

The error in this post caused me to find one of them - and to discover that range check was disabled - so I consider my time well spent.

Edited by Allan Olesen
  • Thanks 1

Share this post


Link to post
5 hours ago, David Heffernan said:

Anyway you should use the debugger to inspect this. Enable debugging for the release build. 

A follow-up to this advice:

 

After correcting the array error, I tested by reverting to the faulty version and enabling debugging information and symbols in the Release configuration. In this changed configuration, I was able to both create the error and debug it.

 

So yes, that would have worked, and I could have saved a lot of time.

 

Thank you.

  • Like 3

Share this post


Link to post

Using the debugger works for most cases.

 

But that (memory overwrite) is exactly the kind of bug where changing some compiler options might make the problem disappear because the memory layout changes. Of course the bug would still be there and simply waiting to bite you in the back.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×