Do I need to assign a default value to the option returned by the Delphi function?

Gradually, I use more options - they can be very useful in certain places for storing data types that are unknown at compile time. One useful value is UnAssigned ("I have no value for you"). I think I discovered long ago that the function:

function DoSomething : variant; begin If SomeBoolean then Result := 4.5 end; 

turned out to be equivalent:

 function DoSomething : variant; begin If SomeBoolean then Result := 4.5 else Result := Unassigned; // <<<< end; 

I suggested that this option should be created dynamically, and if SomeBoolean was FALSE, the compiler created it, but it was "Unassigned" (<> nil?). To further encourage this thinking, the compiler does not report any warnings if you omit the purpose of the result.

Only now I found an unpleasant error where my first example (where "Result" is not the default explication of "nil") actually returned the "old" value from another place.

Should I ALWAYS assign the result (as with prefixed types) while saving the option?

+6
source share
2 answers

Yes, you always need to initialize Result functions , even if it is a managed type (e.g. string and Variant ). The compiler does generate some code to initialize the future return value of the Variant function for you (at least for the Delphi 2010 compiler that I used for testing purposes), but the compiler does not guarantee that your result is initialized; This only makes testing difficult, because you may encounter a situation where your result has been initialized, base your decisions on this only to later find out that your code is buggy, because under certain circumstances the result was not initialized.

From my research, I noticed:

  • If your result is assigned to a global variable, your function is called with an initialized hidden temporary variable, creating the illusion that the result has been magically initialized.
  • If you perform two assignments for the same global variable, you will get two different hidden temporary variables, reusing the illusion of initializing the result.
  • If you make two assignments to the same global variable, but do not use the global variable between calls, the compiler uses only 1 hidden temporary, inhibition of the previous rule!
  • If your variable is local to the calling procedure, no intermediate hidden local variable is used at all, so the result is not initialized.

Demonstration:

Firstly, it is proved here that the function that returns a Variant receives the hidden parameter var Result: Variant . The following two compilations for the same assembler, shown below:

 procedure RetVarProc(var V:Variant); begin V := 1; end; function RetVarFunc: Variant; begin Result := 1; end; // Generated assembler: push ebx // needs to be saved mov ebx, eax // EAX contains the address of the return Variant, copies that to EBX mov eax, ebx // ... not a very smart compiler mov edx, $00000001 mov cl, $01 call @VarFromInt pop ebx ret 

Next, it's interesting to see how the call for both is configured by complier. This is what happens when the procedure is called with the var X:Variant parameter:

 procedure Test; var X: Variant; begin ProcThatTakesOneVarParameter(X); end; // compiles to: lea eax, [ebp - $10]; // EAX gets the address of the local variable X call ProcThatTakesOneVarParameter 

If we make this “X” a global variable, and we call the function that returns Variant, we get this code:

 var X: Variant; procedure Test; begin X := FuncReturningVar; end; // compiles to: lea eax, [ebp-$10] // EAX gets the address of a HIDDEN local variable. call FuncReturningVar // Calls our function with the local variable as parameter lea edx, [ebp-$10] // EDX gets the address of the same HIDDEN local variable. mov eax, $00123445 // EAX is loaded with the address of the global variable X call @VarCopy // This moves the result of FuncReturningVar into the global variable X 

If you look at the prologue of this function, you will notice that the local variable, which is used as a temporary parameter to call FuncReturningVar , is initialized in ZERO. If the function does not contain Result := statements, X will be "Uninitialized". If we call the function again, a DIFFERENT temporary and hidden variable is used! Here are some sample code to see the following:

 var X: Variant; // global variable procedure Test; begin X := FuncReturningVar; WriteLn(X); // Make sure we use "X" X := FuncReturningVar; WriteLn(X); // Again, make sure we use "X" end; // compiles to: lea eax, [ebp-$10] // first local temporary call FuncReturningVar lea edx, [ebp-$10] mov eax, $00123456 call @VarCopy // [call to WriteLn using the actual address of X removed] lea eax, [ebp-$20] // a DIFFERENT local temporary, again, initialized to Unassigned call FuncReturningVar // [ same as before, removed for brevity ] 

If you look at this code, you might think that the "Result" of the function returning Variant is always initialized by Unassigned by the caller. Not true. If in the previous test we make the "X" variable a LOCAL variable (not global), the compiler no longer uses two separate local temporary variables. Thus, we have two separate cases when the compiler generates different code. In other words, do not make any assumptions; always assign Result .

My assumption about a different behavior: if the Variant variable can be obtained outside the current scope, then, as a global variable (or a class field, for that matter), the compiler generates code that uses the thread-safe @VarCopy function. If the variable is local to this function, there are no multithreaded problems, so the compiler can afford to make direct assignments (longer call to @VarCopy).

+8
source

Should I ALWAYS assign a result (as I do when using predefined types) when saving a variant?

Yes.

Check this:

 function DoSomething(SomeBoolean: Boolean) : variant; begin if SomeBoolean then Result := 1 end; 

Use the function as follows:

 var xx: Variant; begin xx := DoSomething(True); if xx <> Unassigned then ShowMessage('Assigned'); xx := DoSomething(False); if xx <> Unassigned then ShowMessage('Assigned'); end; 

xx will still be assigned after the second DoSomething call.

Change the function as follows:

 function DoSomething(SomeBoolean: Boolean) : variant; begin Result := Unassigned; if SomeBoolean then Result := 1 end; 

And xx is not assigned after the second DoSomething call.

+4
source

Source: https://habr.com/ru/post/892303/


All Articles