tags:

views:

592

answers:

7

Hello,

I want to know how to increase the value in a FOR-loop statement.

This is my code.

function Check(var MemoryData:Array of byte;MemorySignature:Array of byte;Position:integer):boolean;
var i:byte;
begin
 for i := 0 to Length(MemorySignature) - 1 do
 begin
  while(MemorySignature[i] = $FF) do inc(i); //<< ERROR <<
  if(memorydata[i + position] <> MemorySignature[i]) then Result:=false;
 end;
 Result := True;
end;

The error is: E2081 Assignment to FOR-Loop variable 'i'.

I'm trying to translate an old code from C# to Delphi,but I can't increase 'i'. Increasing 'i' is not the only way to go,but I want to know where the problem is.

+3  A: 

The problem is that the compiler has taken the original FOR-loop code and assumed it knows what is happening, and thus it can optimize the code by outputting specific CPU instructions that runs the fastest, with those assumptions.

If it allowed you to mess with the variable value, those assumptions might go out the window, and thus the code might not work, and that's why it doesn't allow you to change it.

What you should do instead is just have a separate variable that you're actually using, and only use the FOR-loop indexing variable to keep track of how many iterations you've currently executed.

As an example, a typical optimization might be to write CPU-instructions that will stop iterating when the index register drops to zero, rewriting the loop in such a way that it internally counts down, instead of up, and if you start messing with the variable, it could not rewrite the code like that.

Lasse V. Karlsen
It's not hard for the optimizer to see whether the loop control variable is assigned in the body of the loop. It seems more likely assignment isn't allowed to keep things easier to understand - for the sake of the human reader
Michael Donohue
@Michael: And also because the original Pascal worked that way.
wuub
+6  A: 

in this case, you can just do a 'continue' instead of inc(i)

Michael Donohue
To clarify further, i is incremented automatically (part of the for construct) at the end of each iteration of the for loop.
JeffP
+7  A: 

In addition to what Lasse wrote, assigning to a loop variable is generally considered a code smell. It makes code harder to read (if you want to leave the loop premataturely, you can express that a lot clearer using break/continue), and is often done by accident, causing all kind of nasty side-effects. So instead of jumping through hoops to make the compiler not do its optimizing fu on any loop where the loop variable is touched, Borland (now CodeGear) bit the bullet and made assigning to the loop variable illegal.

If you really want to mess about manually with loop indices, consider using a while-loop.

Paul-Jan
+1 While loop is the way to go, with a well-named boolean test variable. Then you can have a counter analogous to the original, but the boolean test value incorporates both a threshold test for that value and one or more other conditions. Control of loop should be crystal-clear.
Argalatyr
+2  A: 

If you need to alter a loop counter inside a loop, try using a while loop instead.

BTW, you need your Result := True line to be the first line of the function for it to work properly. As it is, it will always return True.

Mike Sutton
+1  A: 

As per Mike Sutton, what you need is a while loop, not a for loop.

function Check(var MemoryData: Array of byte; 
  MemorySignature: Array of byte; Position: Integer):Boolean;
var 
  i:byte;
begin
 Result := True;
 i := 0;
 while i < Length(MemorySignature) do
 begin
   while(MemorySignature[i] = $FF) do 
     Inc(i); 
   if(MemoryData[i + position] <> MemorySignature[i]) then 
     Result := False;
   Inc(i);
 end;
end;

The Delphi implementation of "for" is optimised, but as a result it is less flexible than the C-style

Gerry
+2  A: 

Of course the others are (generally) correct. What wasn't said, is that 'i' in your loop doesn't exist. Delphi uses a CPU register for it. That's why you cannot change it and that's why you should use a 'for' loop (not a 'while') because the 'for' is way faster. Here is your code modified (not tested but I think that you got the idea) - also imho you had some bugs - fixed them also:

function Check(var MemoryData:Array of byte;MemorySignature:Array of byte;Position:integer):boolean;
var i:byte;
begin
 Result := True; //moved at top. Your function always returned 'True'. This is what you wanted?
 for i := 0 to Length(MemorySignature) - 1 do //are you sure??? Perhaps you want High(MemorySignature) here... 
 begin
  if MemorySignature[i] <> $FF then //speedup - '<>' evaluates faster than '='
  begin
   Result:=memorydata[i + position] <> MemorySignature[i]; //speedup.
   if not Result then 
     Break; //added this! - speedup. We already know the result. So, no need to scan till end.
  end;
 end;
end;

...also MemorySignature should have a 'const' or 'var'. Otherwise as it is now the array gets copied. Which means slowdown at each call of 'Check'. Having a 'var' the things are much faster with code unchanged because AFAIS the MemorySignature isn't changed.

HTH

Hey,thanks,I leant very much from your code!I have two questions.Why "Break;" instead of "Exit;"? and When should I use "var" as a function parameter,I couldn't find a useful explanation and you must have at least two I guess!
John
'Break' means "Out of the current LOOP" (for etc.) while 'Exit' means "Out of the current CODE BLOCK" (procedure/function etc.). In our concrete case, yes, *is* the same but (imho) is a good coding habbit to terminate the function at the final 'end'. Of course this is a general rule, not a dogma. Let's suppose that someone else takes the code and he decides that after each check he must wipe (IOW fill with #$00) the MemorySignature, regardless of the function's result. Instinctually he'll add a 'for' loop at the end. If there is an 'Exit' in the middle, then that code *sometimes* is skiped.
There are some ways to pass the parameters. The default one is by value: s:='Foo'; MyFunc(s); Write(s); will write 'Foo' regardless of what is happening inside of MyFunc and this is because 's' is get **copied** inside of the function (in order to protect the 'outside s' from the changes which happen inside of MyFunc) and if 's' is big (many kB) then this can be expensive. Adding a 'var' there, means that _only_ the pointer to 's' (the reference to 's') is passed which is way faster (only few bytes) but this means that any changes which happen inside of your func will be 'visible' outside.
'Const' is anoter variant which combines the advantages of the two above. It means that the parameter is pased **by reference** (hence the call is very fast) but the parameter remains unchanged 'outside'. This is achieved by *prohibiting* the 'change tentative' by the compiler at the compile time. IOW that parameter is **constant** (hence the name: 'const') in the entire function body. Example: function MyFunc(const a: string); begin; a:='Foo'; end; - when you try to compile this, the compiler will throw an error at *a:='Foo';*HTH(Sorry for my big comments :-))
Allocating 'i' to a register doesn't mean it disappears, it merely means there is no address for it. I don't understand the focus on the compiler optimizations in the answers here. The optimizations happen because the language prohibits certain behaviors, not the other way around.
Michael Donohue
@Michael: Perhaps I didn't expressed myself correctly, please forgive me. I don't say that it 'disappear', I said that 'id doesn't exist' (as a **regular** variable) - you can see for yourself looking in the CPU window at the generated code. WRT 'focus on compiler optimizations': It is the *main* reason to use the 'for..to' loop which because of it's 'limitations' is (AFAIK) the **fastest** 'for' from all languages (known by me). Of course, Delphi has also other loops (including 'for..in') but if one can 'accept' the 'for' limitation (imposed by the language btw) one can gain sometimes a lot.
`for` absolutely does not guarantee that index variable will be placed in a register (this should be obvious if only because you can clearly write sufficiently many nested `for`-loops that there are not enough registers to put the variables in!). Furthermore, other variables can (and are) also put into registers by the optimizer. Finally, putting a variable into register has no effect on its mutability - registers can obviously be assigned to just the same as memory locations. Overall, an awful explanation which manages to be both factually incorrect, and distracting from real issue.
Pavel Minaev
A: 

your site is cool :D, thanks for the 'break' explanation, very useful