Allan Olesen 4 Posted June 7, 2023 (edited) 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 June 7, 2023 by Allan Olesen Share this post Link to post
Der schöne Günther 316 Posted June 7, 2023 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
Allan Olesen 4 Posted June 7, 2023 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
Uwe Raabe 2057 Posted June 7, 2023 Does it work when you omit doStuff? 1 Share this post Link to post
David Heffernan 2345 Posted June 7, 2023 (edited) > 'for i:= 0 to 4' does 6 loops when not in Debug mode No it doesn't. Edited June 7, 2023 by David Heffernan Share this post Link to post
Allan Olesen 4 Posted June 7, 2023 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
Uwe Raabe 2057 Posted June 7, 2023 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. 1 Share this post Link to post
David Heffernan 2345 Posted June 7, 2023 Anyway you should use the debugger to inspect this. Enable debugging for the release build. 1 Share this post Link to post
Allan Olesen 4 Posted June 7, 2023 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
PeaShooter_OMO 11 Posted June 7, 2023 (edited) How would this code behave differently between Debug and Release? Do both compile? Edited June 7, 2023 by PeaShooter_OMO Share this post Link to post
David Heffernan 2345 Posted June 7, 2023 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 3 Share this post Link to post
Lajos Juhász 293 Posted June 7, 2023 (edited) 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 June 7, 2023 by Lajos Juhász 1 Share this post Link to post
Allan Olesen 4 Posted June 7, 2023 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
Allan Olesen 4 Posted June 7, 2023 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
David Schwartz 426 Posted June 7, 2023 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
Allan Olesen 4 Posted June 7, 2023 (edited) 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 June 7, 2023 by Allan Olesen 1 Share this post Link to post
Allan Olesen 4 Posted June 7, 2023 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. 3 Share this post Link to post
dummzeuch 1505 Posted June 8, 2023 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. 1 Share this post Link to post