Jump to content
dummzeuch

for loop variable value after the loop

Recommended Posts

I just came across this code:

var
  I: Integer;
begin
  for I := 0 to Collection.Count - 1 do
    if TListColumn(Collection.Items[I]).WidthType <= ColumnTextWidth then Break;
  Changed(I <> Collection.Count);
  WriteCols;
end;

Is it just me or does anybody else think this is wrong?

 

Or is the value of a for loop variable after a loop explicitly defined nowadays? I always thought that's compiler dependent and should not be relied on.

Share this post


Link to post
23 minutes ago, dummzeuch said:

Or is the value of a for loop variable after a loop explicitly defined nowadays? I always thought that's compiler dependent and should not be relied on.

Me too thought that. And it's for this that I start using inline variable, sò no one can use the "index" outside the loop.

 

Bye

Share this post


Link to post
Just now, dummzeuch said:

OK, so that's a bug in the VCL code then.

And where exactly? It would be useful to know...

Share this post


Link to post
34 minutes ago, DelphiUdIT said:

And where exactly? It would be useful to know...

Delphi 12, unit VCL.ComCtrls, method TListColumn.DoChange, line 17287. Probably also in older Delphi versions, I haven't checked.

 

This didn't cause my actual problem though, because it works, even though it is undocumented behavior.

Unfortunately I haven't found the cause, just a workaround, but that's enough for now.

Share this post


Link to post
4 hours ago, dummzeuch said:

Delphi 12, unit VCL.ComCtrls, method TListColumn.DoChange, line 17287. Probably also in older Delphi versions, I haven't checked.

I just now checked, this code exists in TListColumn.DoChange() all the way back to at least Delphi 5 (that the oldest version I have VCL source code for)!

Share this post


Link to post

I think this should be reported.

Something like this I discovered in FastReport and it surfaced in rare circumstances. First when I reported it was ignored till I come with a case to reproduce.

Because this, the report should have also the link to documentation.

  • Like 2
  • Thanks 1

Share this post


Link to post

It is a documentation omission, the help on the link states

 

"You can only rely on the final value of a for loop control variable if the loop is left with a goto or exit statement. "

 

In https://docwiki.embarcadero.com/RADStudio/Athens/en/Declarations_and_Statements_(Delphi)#For_Statements it says

 

"After the for statement terminates (provided this was not forced by a Break or an Exit procedure), the value of counter is undefined. "

 

The value should be defined, as the loop here exits with a Break,

Share this post


Link to post
8 minutes ago, eivindbakkestuen said:

The value should be defined, as the loop here exits with a Break,

Doesn't always exit with a Break

  • Like 1

Share this post


Link to post
On 12/7/2024 at 3:51 PM, dummzeuch said:

I just came across this code:


var
  I: Integer;
begin
  for I := 0 to Collection.Count - 1 do
    if TListColumn(Collection.Items[I]).WidthType <= ColumnTextWidth then Break;
  Changed(I <> Collection.Count);
  WriteCols;
end;

Is it just me or does anybody else think this is wrong?

 

Or is the value of a for loop variable after a loop explicitly defined nowadays? I always thought that's compiler dependent and should not be relied on.

 

"I" is a variable. Its value is known during and after the loop. It's not a good way to write code but it's not wrong. The "for" loop can be seen as a "while I<=LastValue" with a "inc".

It's a reason why so many developers wanted the "for var" syntax.

Share this post


Link to post

A for loop is not a while loop with inc. the compiler might create code that counts down to zero rather than up, if the variable is not used inside the loop.

Edit: Or it might even completely unroll the loop doing away with the variable altogether.

 

But it does not matter: Relying on an undocumented implementation detail is a bad idea, regardless of whether it works or not. The next compiler version or a different compiler e.g. for a different platform might change that detail.

Edited by dummzeuch
  • Like 6

Share this post


Link to post

W1037 FOR-Loop variable '%s' may be undefined after loop (Delphi)

Quote

This warning is issued if the value of a for loop control variable is used after the loop.

You can only rely on the final value of a for loop control variable if the loop is left with a goto or exit statement.

The purpose of this restriction is to enable the compiler to generate efficient code for the for loop.

Also see: What is The Loop Variable After a For Loop in Delphi?

  • Like 2

Share this post


Link to post
On 12/7/2024 at 8:51 AM, dummzeuch said:

I just came across this code:


var
  I: Integer;
begin
  for I := 0 to Collection.Count - 1 do
    if TListColumn(Collection.Items[I]).WidthType <= ColumnTextWidth then Break;
  Changed(I <> Collection.Count);
  WriteCols;
end;

Is it just me or does anybody else think this is wrong?

 

Or is the value of a for loop variable after a loop explicitly defined nowadays? I always thought that's compiler dependent and should not be relied on.

Not just you. Regardless of the variable state at the end of the for loop, a while or repeat loop would be far more clear IMO.

 

  • Like 1

Share this post


Link to post
On 12/8/2024 at 8:07 AM, Remy Lebeau said:

I just now checked, this code exists in TListColumn.DoChange() all the way back to at least Delphi 5 (that the oldest version I have VCL source code for)!

Delphi 2 baby and still there in Delphi 13.

  • Confused 1

Share this post


Link to post
8 hours ago, Lachlan Gemmell said:

Delphi 2 baby and still there in Delphi 13.

They grow up so fast :)

  • Like 2

Share this post


Link to post

It's not wrong, this is a standard pascal behavior and must work. The compiler keep the for value if there is an option to exit loop early. This is normal because if you break a loop mean that you want to know where. 😉
It's not a beautiful code, but is correct, not a bug at all.

Share this post


Link to post
14 minutes ago, SbiriJJ said:

It's not wrong, this is a standard pascal behavior and must work.

And where is this behavior documented? Actual documentation quoted above is wrong then?

Edited by Cristian Peța

Share this post


Link to post
7 minutes ago, Cristian Peța said:

And where is this behavior documented? Actual documentation quoted above is wrong then?

The quoted documentation is right and mean that the code is right. You have a forced exit option so the value of loop counter is preserved.

Edited by SbiriJJ

Share this post


Link to post
3 hours ago, SbiriJJ said:

You have a forced exit option so the value of loop counter is preserved.

But the exit is conditional so the loop value is only guaranteed to be known if the condition was met [*].

In the concrete example...

  for I := 0 to Collection.Count - 1 do
    if TListColumn(Collection.Items[I]).WidthType <= ColumnTextWidth then Break;
  Changed(I <> Collection.Count);

...the value of I will be undefined if all columns have WidthType > ColumnTextWidth which is the case where the code expects I = Collection.Count.

 

*) I will concede that the phrasing of the help is ambiguous and that it's unclear if the loop value is defined even if the exit condition isn't met.

 

FWIW, here's what Danny Thorpe had to say about it back in the day:

Quote

From: "Danny Thorpe" <dthorpe@inprise.com>
Newsgroups: borland.private.fieldtest.iliad.compiler
Subject: Re: Value of counter after a FOR loop
Date: Mon, 7 Aug 2000 19:24:40 -0700

 

The value of the counter variable is theoretically undefined after the loop,
but in practice the Delphi compiler actually goes to some effort to make the
counter variable correct after the loop.  We don't recommend relying on this
(as new optimization levels will make this increasingly difficult to
support), but for now it works.

The reason the value of the counter variable may be difficult to obtain is
loop induction.  The compiler may decide not to use an index counter at all
(when indexing into an array of records), but instead increment a pointer by
the record size.  The compiler may invert the loop counter (though the order
of list traversal is as declared) and may adjust the range to accomodate
zero based or one based array indexing, depending on well that dovetails
into the loop exit condition.

 

-Danny

 

Edited by Anders Melander
  • Like 1

Share this post


Link to post
We don't recommend relying on this 

 

says everything we need to know

  • Like 2

Share this post


Link to post
14 hours ago, SbiriJJ said:

You have a forced exit option

No, it's conditional, as we have said so many times before 

Share this post


Link to post
14 hours ago, Anders Melander said:

The compiler may decide not to use an index counter at all
(when indexing into an array of records), but instead increment a pointer by
the record size.  The compiler may invert the loop counter (though the order
of list traversal is as declared) and may adjust the range to accomodate
zero based or one based array indexing, depending on well that dovetails
into the loop exit condition.

lol I wish

  • Haha 2

Share this post


Link to post
4 hours ago, Stefan Glienke said:

lol I wish

That is just the usual gap between "we'll do that later" and "rather implement nifty cool feature".

Murphy's addendum: If something has not been done right for the first release, it will never get done.

 

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

×