When I call Functions to get a value, I usually initialize varible, in case function fails or doesn't return anything and I want to avoid dealing with uninitialized variable. I do the same for string, integer or any other type.
Example for integer variables:
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
IF vPropValue > 0 Then
...
this it the most common how I use it.
I know I could use:
If GetPropValue(vObject,'Height') > 0 Then
...
but with first example I avoid multiple calls to function, if I need result again later in the code.
Same for string (even though i know local strings are initialized to empty string, while integers are not an can hold any value)
vName := '';
vName := GetObjectName(vObject,'ObjectName');
IF Trim(vPropStrValue) <> '' Then
...
I know I could take steps to avoid duplicate value assignment,like making sure Function returns 0 if everything fails. But I have 100s of functions and I can't rely I never made a mistake how functions handle everything and I'm sure some don't return 0, if everything fails.
I'm trying to understand why this is not desirable practice and how to best avoid it.
EDIT
Here is example where function doesn't return proper value or 0:
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin
vValue:=GetValue(11);
Button1.Caption:=IntToStr(vValue);
end;
In this case the value returned from function is some random number.
In this case the initialization appears to be valid approach. OR NOT?
EDIT 2:
As David pointed out in his answer, correct, there was a warning
[dcc32 Warning] Unit1.pas(33): W1035 Return value of function 'GetValue' might be undefined
but, I ignored it, for no reason, just didn't look there. As it let me compile it, I thought it was OK. So, I did look for the warning and I 'fixed' quite a few functions that had similar issue, because of all IFs Result might not have been defined.
EDIT 3 & CONCLUSION:
I hope it adds to the scope of question and explanation:
Maybe an example of another twist that I use in most of my Functions, would also explain why I thought my initialization of variable was needed, is that I wasn't sure my Functions would behave correctly all the times, especially in case of nested function. Mots of them still are set like this:
function GetProperty(vType:integer):integer;
begin
Try
if vType = 99 then
Result:=GetDifferentProperty(vType)// <-- Call to another Function, that could return whatever...
else
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
except
end;
end;
Now I'm addressing these Try Except End; but some functions are 10 years old and to expect them to work 100%, based on my experience back then, is not something to rely on.
As the only developer on this project, I assume I should trust my functions (and he rest of my code), but I can't imagine in multiple developers environment that all function are set up properly.
So my conclusion: since I didn't take care of the basics - properly designed Functions, I need to have all these checks (variable initialization, Try Except lines..) and probably some other unneccessary stuff.
Assuming that vPropValue is a local variable then this code
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
is indistinguishable from
vPropValue := GetPropValue(vObject,'Height');
A simpler example might be like so:
i := 0;
i := 1;
What is the point of assigning 0 to i and then immediately assigning 1 to i? So, you would surely never write that. You would write:
i := 1;
In your code, at the top of this answer, you assign twice to the same variable. The value assigned in the first assignment is immediately replaced by the value assigned in the second assignment. Therefore, the first assignment is pointless and should be removed.
The second example is a little more complex. Assuming that your functions are written correctly, and always assign to their return value, and that vName is a local variable, then
vName := '';
vName := GetObjectName(vObject,'ObjectName');
is indistinguishable from
vName := GetObjectName(vObject,'ObjectName');
The reason why I have added an extra proviso relates to a quirk of the implementation of function return values, discussed below. The difference between this case and the case above is the return value type. Here it is a managed type, string, whereas in the first example the type is a simple Integer.
Again, given the proviso about the function always assigning to the return value, the first assignment is pointless because the value is immediately replaced. Remove that first assignment.
Regarding the function in your edit, the compiler will warn you of its erroneous implementation if you enable hints and warnings. The compiler will tell you that not all code paths return a value.
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200;
end;
If neither condition is met, then no value is assigned to the result variable. This function should be:
function GetValue(vType:integer):integer;
begin
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200
else
Result:=0;
end;
I cannot stress how important it is that you always return a value from a function. In fact it is a terrible weakness that Delphi even allows your function to be compiled.
The reason that your double assignment sometimes appears useful to you is due to a quirk of of the implementation of function return values in Delphi. Unlike almost all other languages a Delphi function return value for certain more complex types is actually a var parameter. So this function
function foo: string;
is actually, semantically, the same as this:
procedure foo(var result: string);
This is a really odd decision made by the Delphi designers. In most other languages, like C, C++, C#, Java etc., a function return value is like a by-value parameter passed from callee to caller.
This means that you can, if you wish to be perverse, pass values into a function via its return value. For instance, consider this code:
// Note: this code is an example of very bad practice, do not write code like this
function foo: string;
begin
Writeln(Result);
end;
procedure main;
var
s: string;
begin
s := 'bar';
s := foo;
end;
When you call main, it will output bar. This is a rather strange implementation detail. You should not rely on it. Let me repeat myself. You should not rely on it. Do not get in the habit of initializing return values at the call site. That leads to unmaintainable code.
Instead follow the simple rule of ensuring that the function return value is always assigned by the function, and never read before it has been assigned.
More detail on the implementation of function return values is provided by the documentation, with my emphasis:
The following conventions are used for returning function result
values.
Ordinal results are returned, when possible, in a CPU register. Bytes are returned in AL, words are returned in AX, and double-words
are returned in EAX.
Real results are returned in the floating-point coprocessor's top-of-stack register (ST(0)). For function results of type Currency,
the value in ST(0) is scaled by 10000. For example, the Currency value
1.234 is returned in ST(0) as 12340.
For a string, dynamic array, method pointer, or variant result, the effects are the same as if the function result were declared as an
additional var parameter following the declared parameters. In other
words, the caller passes an additional 32-bit pointer that points to a
variable in which to return the function result.
Int64 is returned in EDX:EAX.
Pointer, class, class-reference, and procedure-pointer results are returned in EAX.
For static-array, record, and set results, if the value occupies one byte it is returned in AL; if the value occupies two bytes it is
returned in AX; and if the value occupies four bytes it is returned in
EAX. Otherwise, the result is returned in an additional var parameter
that is passed to the function after the declared parameters.
The following code (A)
vPropValue := 0;
vPropValue := GetPropValue(vObject,'Height');
is indistinguishable from (B)
vPropValue := GetPropValue(vObject,'Height');
The question of whether GetPropValue is "written correctly" is totally irrelevant.
Let's consider what happens even if you did write GetPropValue incorrectly E.g.
function GetPropValue(AObject: TObject; AStr: String): Integer;
begin
if AStr = 'Hello' then Result := 5;
end;
As you know when the input AStr is anything other than "Hello" the result of the function will be pretty much random. (For the sake of the discussion, lets assume it will return -42.)
Code block (A) will do the following:
Set vPropValue to 0
Then set vPropValue to - 42
Code block (B) will simply set vPropValue to - 42 immediately.
TIP: There is no point in writing a wasteful line of code just because you're worried you might have made a mistake in a function you call.
First, As David points out, you can avoid many mistakes simply by paying attention to your compiler hints and warnings.
Second, that sort of "paranoid" coding simply leads to more wasteful code because now you have to start considering invalid values as possible results.
This becomes worse when one day your "safe-value" is actually a valid value. E.g. how would you tell the difference between "default 0" and "correctly returned 0"?
Don't make programming artificially difficult by bloating code with unnecessary redundancies.
Side Note
There are a couple of special situations where the code can behave differently. But you should in any case avoid the designs that lead to those situations because they make it much more difficult to maintain the code.
I mention them purely for the sake of completeness, the advice above still stands.
1) if vPropValue is implemented as a property, the setter could have side-effects that cause different behaviour. While there's nothing wrong with properties, when they do unexpected things you have a serious problem on your hands.
2) if vPropValue is a field on the class (or worse a global variable), then (A) and (B) could behave differently but only if GetPropValue raises an exception. This is because the exception would prevent the result from being assigned. Note this is something to avoid except in special cases because it does make it more difficult to reason about what your code is doing.
And in fact, this is something that makes it so much more important to avoid the redundant initialisations. You want your special case code to look stand out from the rest.
Scavenging my advices from top-level comments in case Pastebin fails
function GetValueUpdate3(vType:integer):integer;
begin
// with SOME types like Integer you can use Pascal case-block instead of error-prone many-ifs-ladder
case vType of
99: Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
1: Result:=100;
3..9: Result:=200;
else Result := 12345; // initialization with safe default value for illegal input like vType=2
end; // case-block
end;
function GetValueUpdate3e(vType:integer):integer;
begin
case vType of
99: Result:=GetDifferentProperty(vType);// <-- Call to another Function, that could return whatever...
1: Result:=100;
3..9: Result:=200;
else raise EInvalidArgument.Create('prohibited value - GetValue( ' + IntToStr(vType) +' )' );
// runtime eror when vType = 2 or any other illegal input
end;
end;
function GetValueUpdate1(vType:integer):integer;
begin
Result := 12345; // initialization with safe default value;
if vType=1 then Exit(100); // special value for special case #1
if (vType>2) and (vType<=9) then Exit(200); // special value for special case #2
// exit with default value
end;
procedure TForm1.Button1Click(Sender: TObject);
var vValue:integer;
begin
vValue:=GetValue(11);
Button1.Caption:=IntToStr(vValue);
end;
// http://stackoverflow.com/questions/33927750
You can use Assertions to validate if your input is correct.
Assertions help a lot in finding logical errors in your code.
Using assertions forces you to think about valid and invalid input data and helps writing better code.
Enabling and disabling assertions at compile time is done via the \$C or \$ASSERTIONS compiler (global switch)
In your example the function GetValue could use an assertion as follows:
function GetValue(vType:integer):integer;
begin
Assert((vType >= 1) and (vType <=9), 'Invalid input value in function GetValue');
if vType=1 then
Result:=100
else if (vType>2) and (vType<=9) then
Result:=200
else Result := 0; // always catch the last else!
end;
In addition every if-statement should catch the final else! (In my opinion ALWAYS!)
Related
I'm redesigning some of my For-loops to run faster. Especially the ones that call functions to compare values with. I'm wondering if my basic Delphi knowledge is missing something really useful.
I have a lot of For-loops similar to this:
for i :=1 to 10000 do
begin
//--- Check if Condition for each Object (i) Property = Yes, Y or a Number ---
if (fGetPropValue(i,'Condition')='Yes') Or (fGetPropValue(i,'Condition')='Y') Or (StrToIntDef(fGetPropValue(i,'Condition'),0)>0)
then
// code...
end;
In this case function gets called 3 times, if result is different than Yes or Y. If it's Yes, it's only called once and skips the rest of calls.
I can use single call and store value to variable, like this:
for i :=1 to 10000 do
begin
//--- Get Condition for each Object (i) Property ---
vCondition:=fGetPropValue(i,'Condition');
//--- Check Condition = Yes, Y or a number ---
if (vCondition='Yes') Or (vCondition='Y') Or (StrtoIntDef(vCondition,0)>0)
then
// code...
end;
This is good, but what in case where I need to compare more complex, like:
for i :=1 to 10000 do
begin
if (fGetPropValue(i,'Condition')='Yes') Or (fGetPropValue(i,'Condition')='Y') Or (StrtoIntDef(fGetPropValue(i,'Condition'),0)>0) And
(fGetPropValue(i,'Enabled')='Yes') Or (fGetPropValue(i,'Enabled')='Y') Or (StrtoIntDef(fGetPropValue(i,'Enabled'),0)>0) And
(fGetPropValue(i,'Visible')='Yes') Or (fGetPropValue(i,'Visible')='Y') Or (StrtoIntDef(fGetPropValue(i,'Visible'),0)>0) And
(fGetPropValue(i,'AllowAccess')='Yes') Or (fGetPropValue(i,'AllowAccess')='Y') Or (StrtoIntDef(fGetPropValue(i,'AllowAccess'),0)>0)
then
// code...
end;
In this case if I introduce 4 variable and get all 4 values, I eliminate the speedy option if first logical comparison is true - without 4 variable it will not execute anymore calls.
Any way to redesign complex comparison?
You need to extract this into a function that you can re-use:
function PropValueIsTrue(const PropValue: string): Boolean;
begin
Result := (PropValue='Yes') or (PropValue='Y') or (StrtoIntDef(PropValue,0)>0);
end;
Then your code becomes:
if PropValueIsTrue(fGetPropValue(i,'Condition')) and
PropValueIsTrue(fGetPropValue(i,'Enabled')) and
PropValueIsTrue(fGetPropValue(i,'Visible')) and
PropValueIsTrue(fGetPropValue(i,'AllowAccess')) then
Recently I've ported a huge chunk of code form C++ to Delphi (over 1mb of C code). The code is full with pointers. Despite project compiling and working well for 99% of the time, I'm seeing some weird results from time to time, which leads me to think there might be bugs with pointer handling - indeed, I've already found a couple. The problem is that they are really hard to track down without any clues/hints from compiler.
Maybe you have some tips, for such cases:
var
a: PSingle;
GetMem(a, SizeOf(Single));
a^ := 1.1;
// Call func declared as - someFunc(aIn: PSingle);
someFunc(#a); // <<-- Bug here. I mistakely added # while porting.
// someFunc needs PSingle, but gets a PPSingle instead
In the example, there's erroneous # inserted. Program does not crash, just deals with that erroneous data and keeps running. I need a way of finding such cases where "Pointer to a Pointer to a Value" gets passed instead of "Pointer to a Value".
How do you track down pointer bugs like these?
The "typed # operator" compiler option is designed to detect exactly that kind of mistake. When it's disabled, the expression #a has type Pointer, which is compatible with all pointer types. Enable the option, and the same expression has type ^PSingle, which is not compatible with the expected PSingle.
I recommend turning on that option in all projects; it baffles me that Delphi doesn't make that option the default all the time.
You can modify the state of that option within your code with the $T+ and $T- compiler directives.
Another thing you can do is convert your code to use more idiomatic Delphi. For example, pass the argument by reference instead of by pointer value. Change the definition of the argument:
procedure someFunc(var arg: Single);
With that declaration, passing #a will be an error that the compiler will find and forbid. You would instead pass a^, or you could even get rid of the pointer entirely and just declare a as a plain Single rather than PSingle.
Just because the original code was written in C and C-style C++, it doesn't mean your Delphi code has to look like it.
The problem is here:
someFunc(#a);
You are adding a pointer to a pointer not the pointer it self.
Let me make you an example:
uses
Math;
function Somefunc(value: pSingle): Single;
begin
Result := (value^ + 1.1)
end;
procedure TForm23.FormCreate(Sender: TObject);
var
a: pSingle;
b: Single;
begin
GetMem(a, SizeOf(Single));
a^ := 1.1;
//Example 1:
b := Somefunc(a);
Caption := BoolToStr(CompareValue(2.2, b, 0.00001) = 0., True); //Caption: True
//Example 2:
b := Somefunc(#a); //NOTE #a
Caption := BoolToStr(CompareValue(1.1, b, 0.00001) = 0., True); //Caption: True
FreeMem(a);
end;
As you see in the first example the actual value are changed, while in example 2 the value og a remains unchanged because you parse the pointer to the pointer and therefor it is the pointer you chanhe and not the value.
Optimization on/off doesn't matter. This is simplified code to demonstrate the warning. In the original routine all assignments and compares are to function expressions that could return a variety of values.
procedure test;
var i, k: integer;
begin
k := 21;
repeat
if k = 20 then break;
i := 5
until i = 5;
end;
This does seem to be a weakness in the compiler.
repeat
if k = 20 then break;
i := 5
until i = 5;
A human static analysis can easily check that i is always assigned before it is read. The line before the until assigns it. If that assignment is skipped by the break, then the until test is also skipped.
So, this can only be described as a compiler bug because the compiler should be able to understand how break and until interact. Clearly the compiler's analysis depends on an understanding of these things, since removing the break will also remove the warning. So it can only be that the compiler doesn't understand well enough.
It turns out that the 32 bit Windows compiler still behaves the same way in the current Delphi release, XE7. But the 64 bit compiler correctly emits no warning for your code.
Note that you might expect the compiler to realise that the condition in the if test in your code always evaluates False. Well, the compiler won't. It does not perform static analysis of constant propagation through non constant variables. Its analysis takes no account of the values that you place in variables.
The reason why Delphi warns you about this is the fact that local variables don't get initialized automatically. What this means? This means that if you try reading any such variable it will return some more or less random result (contents of the memory it points to).
So as soon as Delphi recognizes a posibility that some variable might be read before anything is written to it (initialization of a variable does write a default value into it) the warning will be raised.
And this would happen in your code if the value of "k" would be 20 becouse the "i := 5" line would be skipped.
How do you solve this warning in your case? Simply set some value to "i" outside any loops or any conditional statments. For instance:
procedure test;
var i, k: integer;
begin
//Set initial value for i
i := 0;
k := 21;
repeat
if k = 20 then break;
i := 5
until i = 5;
end;
I was really impressed with this delphi two liner using the IFThen function from Math.pas. However, it evaluates the DB.ReturnFieldI first, which is unfortunate because I need to call DB.first to get the first record.
DB.RunQuery('select awesomedata1 from awesometable where awesometableid = "great"');
result := IfThen(DB.First = 0, DB.ReturnFieldI('awesomedata1'));
(as a pointless clarification, because I've got so many good answers already. I forgot to mention that 0 is the code that DB.First returns if it's got something in it, might not have made sense otherwise)
Obviously this isn't such a big deal, as I could make it work with five robust liners. But all I need for this to work is for Delphi to evaluate DB.first first and DB.ReturnFieldI second. I don't want to change math.pas and I don't think this warrants me making a overloaded ifthen because there's like 16 ifthen functions.
Just let me know what the compiler directive is, if there is an even better way to do this, or if there is no way to do this and anyone whose procedure is to call db.first and blindly retrieve the first thing he finds is not a real programmer.
The evaluation order of expressions is commonly undefined. (C and C++ are the same way. Java always evaluates left-to-right.) The compiler offers no control over it. If you need two expressions to be evaluated in a specific order, then write your code differently. I wouldn't really worry about the number of lines of code. Lines are cheap; use as many as you need. If you find yourself using this pattern often, write a function that wraps it all up:
function GetFirstIfAvailable(DB: TDatabaseObject; const FieldName: string): Integer;
begin
if DB.First = 0 then
Result := DB.ReturnFieldI(FieldName)
else
Result := 0;
end;
Your original code probably wouldn't have been what you wanted, even if evaluation order were different. Even if DB.First wasn't equal to zero, the call to ReturnFieldI would still be evaluated. All actual parameters are fully evaluated before invoking the function that uses them.
Changing Math.pas wouldn't help you anyway. It doesn't control what order its actual parameters are evaluated in. By the time it sees them, they've already been evaluated down to a Boolean value and an integer; they're not executable expressions anymore.
The calling convention can affect evaluation order, but there's still no guarantee. The order that parameters are pushed onto the stack does not need to match the order in which those values were determined. Indeed, if you find that stdcall or cdecl gives you your desired evaluation order (left-to-right), then they are being evaluated in the reverse order of the one they're passed with.
The pascal calling convention passes arguments left-to-right on the stack. That means the leftmost argument is the one at the bottom of the stack, and the rightmost argument is at the top, just below the return address. If the IfThen function used that calling convention, there are several ways the compiler could achieve that stack layout:
The way you expect, which is that each argument is evaluated and pushed immediately:
push (DB.First = 0)
push DB.ReturnFieldI('awesomedata1')
call IfThen
Evaluate arguments right-to-left and store the results in temporaries until they're pushed:
tmp1 := DB.ReturnFieldI('awesomedata1')
tmp2 := (DB.First = 0)
push tmp2
push tmp1
call IfThen
Allocate stack space first, and evaluate in whatever order is convenient:
sub esp, 8
mov [esp], DB.ReturnFieldI('awesomedata1')
mov [esp + 4], (DB.First = 0)
call IfThen
Notice that IfThen receives the argument values in the same order in all three cases, but the functions aren't necessarily called in that order.
The default register calling convention also passes arguments left-to-right, but the first three arguments that fit are passed in registers. The registers used to pass arguments, though, are also the registers most commonly used for evaluating intermediate expressions. The result of DB.First = 0 needed to be passed in the EAX register, but the compiler also needed that register for calling ReturnFieldI and for calling First. It was probably a little more convenient to evaluate the second function first, like this:
call DB.ReturnFieldI('awesomedata1')
mov [ebp - 4], eax // store result in temporary
call DB.First
test eax, eax
setz eax
mov edx, [ebp - 4]
call IfThen
Another thing to point out is that your first argument is a compound expression. There's a function call and a comparison. There's nothing to guarantee that those two parts are performed consecutively. The compiler might get the function calls out of the way first by calling First and ReturnFieldI, and afterward compare the First return value against zero.
The calling convention affects the way they are evaluated.
There is not a compiler define to control this.
Pascal is the calling convention you would have to use to get this behavior.
Although I would personally never depend on this type of behavior.
The following example program demonstrates how this works.
program Project2;
{$APPTYPE CONSOLE}
uses SysUtils;
function ParamEvalTest(Param : Integer) : Integer;
begin
writeln('Param' + IntToStr(Param) + ' Evaluated');
result := Param;
end;
procedure TestStdCall(Param1,Param2 : Integer); stdCall;
begin
Writeln('StdCall Complete');
end;
procedure TestPascal(Param1,Param2 : Integer); pascal;
begin
Writeln('Pascal Complete');
end;
procedure TestCDecl(Param1,Param2 : Integer); cdecl;
begin
Writeln('CDecl Complete');
end;
procedure TestSafecall(Param1,Param2 : Integer); safecall;
begin
Writeln('SafeCall Complete');
end;
begin
TestStdCall(ParamEvalTest(1),ParamEvalTest(2));
TestPascal(ParamEvalTest(1),ParamEvalTest(2));
TestCDecl(ParamEvalTest(1),ParamEvalTest(2));
TestSafeCall(ParamEvalTest(1),ParamEvalTest(2));
ReadLn;
end.
This would require you to write your own IfThen Functions.
If you really want this to be a one liner you really can do that in Delphi. I just think it looks ugly.
If (DB.First = 0) then result := DB.ReturnFieldI('awesomedata1') else result := 0;
Can't you change your query to have only one result so avoid to do the 'First' command ?
Just like :
SELECT TOP 1 awesomedata1 from awesometable
In Access...
AFAIK there is no compiler directive to control this. Unless you use the stdcall/cdecl/safecall conventions, parameters are passed left to right on the stack, but because the default register convention can pass parameters in the registers as well, it could happen that a parameter is calculated later an put in a register just before the call. And because only the register order is fixed (EAX, EDX, ECX) for parameters that qualify, registers can be loaded in any order. You could try to force a "pascal" calling convention (you'd need to rewrite the function, anyway) but IMHO is always dangerous to rely on such kind of code, if the compiler can't explicitly guarantee the order of evaluation. And imposing an evaluation order may greatly reduce the number of optimizations available.
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.
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
in this case, you can just do a 'continue' instead of inc(i)
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.
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.
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.
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