Nie tak dawno zadałem podobne pytanie dotyczące niejawnych zmiennych interfejsu.
Źródłem tego pytania był błąd w moim kodzie, ponieważ nie byłem świadomy istnienia niejawnej zmiennej interfejsu utworzonej przez kompilator. Ta zmienna została sfinalizowana, gdy procedura, która była jej właścicielem, zakończyła się. To z kolei spowodowało błąd, ponieważ czas życia zmiennej był dłuższy niż się spodziewałem.
Teraz mam prosty projekt ilustrujący kilka interesujących zachowań kompilatora:
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
jest kompilowany tak, jak można sobie wyobrazić. Zmienna lokalna I
, wynik funkcji, jest przekazywana jako niejawny var
parametr do Create
. Porządkowanie StoreToLocal
wyników w jednym telefonie do IntfClear
. Żadnych niespodzianek.
Jednak StoreViaPointerToLocal
jest traktowany inaczej. Kompilator tworzy niejawną zmienną lokalną, do której przekazuje Create
. Kiedy Create
zwraca, P^
wykonywane jest przypisanie do . To pozostawia procedurę z dwiema zmiennymi lokalnymi przechowującymi odwołania do interfejsu. Porządkowanie StoreViaPointerToLocal
wyników w dwóch wezwaniach IntfClear
.
Skompilowany kod StoreViaPointerToLocal
wygląda następująco:
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
Mogę się domyślić, dlaczego kompilator to robi. Jeśli może udowodnić, że przypisanie do zmiennej wynikowej nie spowoduje wyjątku (tj. Jeśli zmienna jest lokalna), wówczas używa zmiennej wynikowej bezpośrednio. W przeciwnym razie używa niejawnego lokalnego i kopiuje interfejs po zwróceniu funkcji, zapewniając w ten sposób, że nie wyciekniemy odwołania w przypadku wyjątku.
Ale nie mogę znaleźć żadnego stwierdzenia na ten temat w dokumentacji. Ma to znaczenie, ponieważ żywotność interfejsu jest ważna i jako programista musisz mieć możliwość wpływania na niego od czasu do czasu.
Więc czy ktoś wie, czy istnieje dokumentacja tego zachowania? Jeśli nie, to czy ktoś ma o tym więcej wiedzy? Jak obsługiwane są pola instancji, jeszcze tego nie sprawdziłem. Oczywiście mógłbym wypróbować to wszystko samodzielnie, ale szukam bardziej formalnego oświadczenia i zawsze wolę unikać polegania na szczegółach implementacji opracowanych metodą prób i błędów.
Zaktualizuj 1
Odpowiadając na pytanie Remy'ego, ważne było dla mnie, kiedy musiałem sfinalizować obiekt za interfejsem przed wykonaniem kolejnej finalizacji.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Jak napisano w ten sposób, jest w porządku. Ale w prawdziwym kodzie miałem drugi domyślny lokalny, który został sfinalizowany po wydaniu GIL i zbombardowaniu. Rozwiązałem problem, wyodrębniając kod z GIL Acquire / Release do oddzielnej metody i tym samym zawęziłem zakres zmiennej interfejsu.
źródło
procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
Odpowiedzi:
Jeśli istnieje jakakolwiek dokumentacja tego zachowania, prawdopodobnie będzie to obszar tworzenia przez kompilator zmiennych tymczasowych do przechowywania wyników pośrednich podczas przekazywania wyników funkcji jako parametrów. Rozważ ten kod:
procedure UseInterface(foo: IInterface); begin end; procedure Test() begin UseInterface(Create()); end;
Kompilator musi utworzyć niejawną zmienną tymczasową, aby przechowywać wynik Create, gdy jest on przekazywany do UseInterface, aby upewnić się, że interfejs ma okres istnienia> = czas życia wywołania UseInterface. Ta niejawna zmienna tymczasowa zostanie usunięta na końcu procedury, do której należy, w tym przypadku na końcu procedury Test ().
Możliwe, że przypadek przypisania wskaźnika może należeć do tego samego zasobnika, co przekazywanie wartości interfejsu pośredniego jako parametrów funkcji, ponieważ kompilator nie „widzi”, dokąd zmierza wartość.
Pamiętam, że na przestrzeni lat w tym obszarze było kilka błędów. Dawno temu (D3? D4?) Kompilator w ogóle nie liczył wartości pośredniej. Działało przez większość czasu, ale wpadało w kłopoty w sytuacjach aliasów parametrów. Wydaje mi się, że po tym, jak się tym zajęto, podjęto dalsze działania w zakresie parametrów konst. Zawsze istniała chęć przeniesienia usuwania interfejsu wartości pośredniej na tak szybko, jak to możliwe po instrukcji, w której był potrzebny, ale nie sądzę, aby kiedykolwiek został zaimplementowany w optymalizatorze Win32, ponieważ kompilator po prostu nie był ustawiony do obsługi dyspozycji na podstawie instrukcji lub bloku.
źródło
Nie można zagwarantować, że kompilator nie zdecyduje się na utworzenie tymczasowej niewidocznej zmiennej.
A nawet jeśli to zrobisz, wyłączona optymalizacja (lub nawet ramki stosu?) Może zepsuć twój doskonale sprawdzony kod.
I nawet jeśli uda Ci się przejrzeć swój kod pod wszystkimi możliwymi kombinacjami opcji projektu - kompilacja kodu w ramach czegoś takiego jak Lazarus lub nawet nowa wersja Delphi przyniesie piekło z powrotem.
Najlepszym rozwiązaniem byłoby użycie zasady „zmienne wewnętrzne nie mogą przetrwać rutyny”. Zwykle nie wiemy, czy kompilator utworzyłby jakieś wewnętrzne zmienne, czy nie, ale wiemy, że wszelkie takie zmienne (jeśli zostałyby utworzone) zostałyby sfinalizowane, gdy istnieje procedura.
Dlatego jeśli masz taki kod:
// 1. Some code which may (or may not) create invisible variables // 2. Some code which requires release of reference-counted data
Na przykład:
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;
Następnie należy po prostu zawinąć blok „Praca z interfejsem” do podprogramu:
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;
To prosta, ale skuteczna zasada.
źródło