Необходимо ли присваивать значение по умолчанию варианту, возвращаемому из функции Delphi?
Постепенно я стал использовать больше вариантов-они могут быть очень полезны в определенных местах для переноса типов данных, которые не известны во время компиляции. Одно полезное значение не назначено ("у меня нет значения для вас"). Мне кажется, я давно обнаружил, что функция:
function DoSomething : variant;
begin
If SomeBoolean then
Result := 4.5
end;
, по-видимому, эквивалентно:
function DoSomething : variant;
begin
If SomeBoolean then
Result := 4.5
else
Result := Unassigned; // <<<<
end;
Я предположил, что вариант должен быть создан динамически, и если SomeBoolean был ложным, компилятор создал его, но он был "Неназначен" ( ноль?). Чтобы еще больше стимулировать это мышление, компилятор не сообщает о предупреждении, если вы опускаете присвоение результата.
Только что я заметил неприятную ошибку, когда мой первый пример (где 'Result' не является эксплицитным значением по умолчанию 'nil') фактически вернул "старое" значение откуда-то еще.
Должен ли я всегда назначать результат (как я делаю при использовании префиксных типов) при повторном выборе варианта?
2 ответа:
Да, вам всегда нужно инициализировать
Result
функции , даже если это управляемый тип (например,string
иVariant
). Компилятор действительно генерирует некоторый код для инициализации будущего возвращаемого значения функцииVariant
для вас (по крайней мере, компилятор Delphi 2010, который я использовал для тестирования, делает это), но компилятор не гарантирует, что ваш результат будет инициализирован; это только усложняет тестирование, потому что вы можете столкнуться с ситуацией, когда ваш результат был инициализирован, основывая свои решения на это только для того, чтобы позже обнаружить, что ваш код глючит, потому что при определенных обстоятельствах результат не был инициализирован.Из моего исследования я заметил:
- Если ваш результат присваивается глобальной переменной, ваша функция вызывается с инициализированной скрытой временной переменной, создавая иллюзию, что результат магически инициализирован.
- Если вы сделаете два назначения одной и той же глобальной переменной, вы получите два различных скрытых временных переменные, усиливающие иллюзию того, что результат инициализирован.
- Если вы делаете два назначения одной и той же глобальной переменной, но не используете глобальную переменную между вызовами, компилятор использует только 1 скрытое временное, тормозящее предыдущее правило!
- Если ваша переменная является локальной для вызывающей процедуры, то никакая промежуточная скрытая локальная переменная не используется вообще, поэтому результат не инициализируется.
Демонстрация:
Во-первых, вот доказательство того, что a функция, возвращающая
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 " глобальной переменной и вызовем функцию, возвращающую вариант, мы получим следующий код:
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, всегдаинициализируется в Unassigned вызовом вечеринка. Неправда. Если в предыдущем тесте мы сделаем переменную "X" локальной (а не глобальной), компилятор больше не будет использовать две отдельные локальные временные переменные. Таким образом, мы имеем два отдельных случая, когда компилятор генерирует различный код. Другими словами, Не делайте никаких предположений, всегда назначайте
Result
.Мое предположение о другом поведении: если переменная
Variant
может быть доступна вне текущей области, как глобальная переменная (или поле класса, если на то пошло), то компилятор генерирует код, который использует потокобезопасную функцию @ VarCopy. Если переменная локальна для функции, нет проблем с многопоточностью, поэтому компилятор может взять на себя смелость выполнять прямые назначения (больше не вызывая @VarCopy).
Должен ли я всегда назначать результат (как я это делаю при использовании предопределенных типов), когда повторный вариант?
Да.
Проверьте это:
function DoSomething(SomeBoolean: Boolean) : variant; begin if SomeBoolean then Result := 1 end;
Используйте функцию следующим образом:
var xx: Variant; begin xx := DoSomething(True); if xx <> Unassigned then ShowMessage('Assigned'); xx := DoSomething(False); if xx <> Unassigned then ShowMessage('Assigned'); end;
Xx все равно будет назначен после второго вызова DoSomething.
Измените функцию следующим образом:
function DoSomething(SomeBoolean: Boolean) : variant; begin Result := Unassigned; if SomeBoolean then Result := 1 end;
И xx не назначается после второго вызова DoSomething.