Да, вам всегда нужно инициализировать Result
функции , даже если это управляемый тип (например, string
и Variant
).Компилятор генерирует некоторый код для инициализации будущего возвращаемого значения функции Variant
для вас (по крайней мере, это делает компилятор Delphi 2010, который я использовал для тестирования), но компилятор не гарантирует инициализацию вашего результата;Это только усложняет тестирование, потому что вы можете столкнуться со случаем, когда ваш Результат был инициализирован, основывать свои решения на этом, только чтобы потом обнаружить, что ваш код содержит ошибки, потому что при определенных обстоятельствах Результат не инициализируется .
Из моего исследования я заметил:
- Если ваш результат присваивается глобальной переменной, ваша функция вызывается с инициализированной скрытой временной переменной, создавая иллюзию, чтоРезультат магически инициализирован.
- Если вы сделаете два присвоения одной и той же глобальной переменной, вы получите две разные скрытые временные переменные, что усилит иллюзию инициализации Результата.
- Есливы делаете два назначения одной и той же глобальной переменной, но не используете глобальную переменную между вызовами, компилятор использует только 1 скрытое временное значение, нарушая предыдущее правило!
- Если ваша переменная является локальной для вызывающей процедуры, нетпромежуточная скрытая локальная переменная используется вообще, поэтомуРезультат не инициализирован.
Демонстрация:
Во-первых, вот доказательство того, что функция, возвращающая Variant
, получает скрытый параметр var Result:
Variant
.Следующие два компилируются с точно таким же ассемблером, как показано ниже:
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
Далее, интересно посмотреть, как вызов для этих двух устанавливается компилятором.Вот что происходит для вызова процедуры с параметром var X:Variant
:
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
Если мы сделаем этот «X» глобальной переменной и вызовем функцию, возвращающую Variant, мы получим этот код:
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
Если вы посмотрите на пролог этой функции, вы заметите, что локальная переменная, которая используется в качестве временного параметра для вызова FuncReturningVar
, инициализируется нулем.Если функция не содержит операторов Result :=
, X
будет "Неинициализирован".Если мы снова вызываем функцию, используется РАЗНАЯ временная и скрытая переменная!Вот небольшой пример кода, чтобы увидеть, что:
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 ]
Когда вы смотрите на этот код, вы можете подумать, что «Результатом» функции, возвращающей Variant, является allways , инициализированный как Unassigned byвызывающая сторона.Не правда.Если в предыдущем тесте мы сделали переменную «X» локальной переменной (не глобальной), компилятор больше не использует две отдельные локальные временные переменные.Таким образом, у нас есть два отдельных случая, когда компилятор генерирует другой код.Другими словами, не делайте никаких предположений, всегда присваивайте Result
.
Мое предположение о различном поведении: если переменная Variant
может быть доступна вне текущей области, как глобальная переменная (илив этом случае), компилятор генерирует код, который использует поточно-безопасную функцию @VarCopy.Если переменная является локальной для функции, нет проблем с многопоточностью, поэтому компилятор может свободно выполнять прямые назначения (больше не вызывая @VarCopy).