Hice una pregunta similar sobre las variables de interfaz implícitas no hace mucho tiempo.
La fuente de esta pregunta fue un error en mi código debido a que no estaba al tanto de la existencia de una variable de interfaz implícita creada por el compilador. Esta variable se finalizó cuando finalizó el trámite que la poseía. Esto, a su vez, provocó un error debido a que la vida útil de la variable era más larga de lo que había anticipado.
Ahora, tengo un proyecto simple para ilustrar un comportamiento interesante del compilador:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := @I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal
se compila tal como se imagina. La variable local I
, el resultado de la función, se pasa como var
parámetro implícito a Create
. El orden para StoreToLocal
resultados en una sola llamada a IntfClear
. No hay sorpresas ahí.
Sin embargo, StoreViaPointerToLocal
se trata de manera diferente. El compilador crea una variable local implícita a la que pasa Create
. Cuando Create
regresa, se realiza la asignación a P^
. Esto deja la rutina con dos variables locales que contienen referencias a la interfaz. El tidy up for StoreViaPointerToLocal
da como resultado dos llamadas a IntfClear
.
El código compilado para StoreViaPointerToLocal
es así:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call @IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call @IntfClear
00435C9D C3 ret
Puedo adivinar por qué el compilador está haciendo esto. Cuando puede probar que la asignación a la variable de resultado no generará una excepción (es decir, si la variable es local), entonces usa la variable de resultado directamente. De lo contrario, usa un local implícito y copia la interfaz una vez que la función ha regresado, lo que garantiza que no filtremos la referencia en caso de una excepción.
Pero no puedo encontrar ninguna declaración de esto en la documentación. Es importante porque la vida útil de la interfaz es importante y, como programador, debe poder influir en ella en ocasiones.
Entonces, ¿alguien sabe si hay alguna documentación de este comportamiento? Si no, ¿alguien tiene más conocimiento al respecto? ¿Cómo se manejan los campos de instancia? Aún no lo he verificado. Por supuesto, podría probarlo todo por mí mismo, pero estoy buscando una declaración más formal y siempre prefiero evitar confiar en los detalles de implementación resueltos por prueba y error.
Actualización 1
Para responder a la pregunta de Remy, me importaba cuándo necesitaba finalizar el objeto detrás de la interfaz antes de realizar otra finalización.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Como está escrito así, está bien. Pero en el código real tenía un segundo local implícito que se finalizó después de que se lanzó el GIL y se bombardeó. Resolví el problema extrayendo el código dentro de Adquirir / Liberar GIL en un método separado y así estreché el alcance de la variable de interfaz.
fuente
procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
Respuestas:
Si hay alguna documentación de este comportamiento, probablemente estará en el área de producción del compilador de variables temporales para contener resultados intermedios cuando se pasan resultados de funciones como parámetros. Considere este código:
procedure UseInterface(foo: IInterface); begin end; procedure Test() begin UseInterface(Create()); end;
El compilador tiene que crear una variable temporal implícita para contener el resultado de Create cuando se pasa a UseInterface, para asegurarse de que la interfaz tenga una vida útil> = la vida útil de la llamada a UseInterface. Esa variable temporal implícita se eliminará al final del procedimiento que la posee, en este caso al final del procedimiento Test ().
Es posible que su caso de asignación de puntero caiga en el mismo grupo que pasar valores de interfaz intermedios como parámetros de función, ya que el compilador no puede "ver" a dónde va el valor.
Recuerdo que ha habido algunos errores en esta área a lo largo de los años. Hace mucho tiempo (¿D3? D4?), El compilador no hacía referencia al recuento del valor intermedio en absoluto. Funcionó la mayor parte del tiempo, pero se metió en problemas en situaciones de alias de parámetros. Una vez que se abordó eso, creo que hubo un seguimiento con respecto a los parámetros const. Siempre hubo un deseo de mover la eliminación de la interfaz de valor intermedio lo antes posible después de la declaración en la que se necesitaba, pero no creo que eso se haya implementado en el optimizador de Win32 porque el compilador simplemente no estaba configurado. listo para manipular la eliminación en la granularidad de la declaración o del bloque.
fuente
No puede garantizar que el compilador no decida crear una variable temporal invisible.
E incluso si lo hace, la optimización desactivada (¿o incluso los marcos de pila?) Puede estropear su código perfectamente verificado.
E incluso si logra revisar su código en todas las combinaciones posibles de opciones de proyecto, compilar su código en algo como Lazarus o incluso una nueva versión de Delphi traerá el infierno de vuelta.
Lo mejor sería utilizar la regla "las variables internas no pueden sobrevivir a la rutina". Por lo general, no sabemos si el compilador crearía algunas variables internas o no, pero sí sabemos que dichas variables (si se crean) se finalizarán cuando exista la rutina.
Por lo tanto, si tiene un código como este:
// 1. Some code which may (or may not) create invisible variables // 2. Some code which requires release of reference-counted data
P.ej:
Lib := LoadLibrary(Lib, 'xyz'); try // Create interface P := GetProcAddress(Lib, 'xyz'); I := P; // Work with interface finally // Something that requires all interfaces to be released FreeLibrary(Lib); // <- May be not OK end;
Luego, simplemente debe envolver el bloque "Trabajar con interfaz" en la subrutina:
procedure Work(const Lib: HModule); begin // Create interface P := GetProcAddress(Lib, 'xyz'); I := P; // Work with interface end; // <- Releases hidden variables (if any exist) Lib := LoadLibrary(Lib, 'xyz'); try Work(Lib); finally // Something that requires all interfaces to be released FreeLibrary(Lib); // <- OK! end;
Es una regla simple pero efectiva.
fuente