얼마 전에 암시 적 인터페이스 변수에 대해 비슷한 질문을했습니다 .
이 질문의 원인은 컴파일러에 의해 생성 된 암시 적 인터페이스 변수의 존재를 모르기 때문에 내 코드의 버그였습니다. 이 변수는 자신을 소유 한 프로 시저가 완료 될 때 완성되었습니다. 결과적으로 변수의 수명이 예상보다 길어 버그가 발생했습니다.
이제 컴파일러의 흥미로운 동작을 설명하는 간단한 프로젝트가 있습니다.
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
상상하는대로 컴파일됩니다. I
함수의 결과 인 지역 변수 는에 암시 적 var
매개 변수로 전달됩니다 Create
. StoreToLocal
결과를 정리 하면에 대한 단일 호출이 발생합니다 IntfClear
. 거기에 놀라움이 없습니다.
그러나 StoreViaPointerToLocal
다르게 취급됩니다. 컴파일러는에 전달되는 암시 적 지역 변수를 만듭니다 Create
. 때 Create
반환, 할당 정보는 다음의 제품에 P^
수행됩니다. 그러면 인터페이스에 대한 참조를 보유하는 두 개의 로컬 변수가있는 루틴이 남습니다. 정리 StoreViaPointerToLocal
하면 IntfClear
.
에 대한 컴파일 된 코드 StoreViaPointerToLocal
는 다음과 같습니다.
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
왜 컴파일러가 이것을하고 있는지 짐작할 수 있습니다. 결과 변수에 할당해도 예외가 발생하지 않는다는 것을 증명할 수있는 경우 (즉, 변수가 로컬 인 경우) 결과 변수를 직접 사용합니다. 그렇지 않으면 암시 적 로컬을 사용하고 함수가 반환되면 인터페이스를 복사하여 예외 발생시 참조를 유출하지 않도록합니다.
그러나 문서에서 이에 대한 설명을 찾을 수 없습니다. 인터페이스 수명이 중요하고 프로그래머로서 때때로 영향을 미칠 수 있어야하기 때문에 중요합니다.
이 행동에 대한 문서가 있는지 아는 사람이 있습니까? 아무도 그것에 대해 더 이상 알고 있지 않다면? 인스턴스 필드는 어떻게 처리됩니까? 아직 확인하지 않았습니다. 물론 모든 것을 직접 시도해 볼 수는 있지만 좀 더 공식적인 진술을 찾고 있으며 시행 착오를 통해 구현 된 세부 사항에 의존하지 않는 것을 선호합니다.
업데이트 1
Remy의 질문에 답하기 위해 다른 마무리 작업을 수행하기 전에 인터페이스 뒤의 객체를 마무리해야 할 때가 중요했습니다.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
이렇게 써도 괜찮습니다. 그러나 실제 코드에는 GIL이 출시되고 폭격을받은 후에 완성 된 두 번째 암시 적 로컬이 있습니다. Acquire / Release GIL 내부의 코드를 별도의 메서드로 추출하여 문제를 해결하여 인터페이스 변수의 범위를 좁혔습니다.
답변
이 동작에 대한 문서가 있으면 함수 결과를 매개 변수로 전달할 때 중간 결과를 보유하는 임시 변수의 컴파일러 생성 영역에있을 것입니다. 이 코드를 고려하십시오.
procedure UseInterface(foo: IInterface);
begin
end;
procedure Test()
begin
UseInterface(Create());
end;
컴파일러는 Create의 결과가 UseInterface로 전달 될 때 유지되는 암시 적 임시 변수를 만들어 인터페이스가 UseInterface 호출의 수명보다> = 수명을 갖도록해야합니다. 그 암시 적 임시 변수는 그것을 소유 한 프로 시저의 끝,이 경우 Test () 프로 시저의 끝에서 처리됩니다.
컴파일러가 값이 어디로 가는지 “볼”수 없기 때문에 포인터 할당 사례가 중간 인터페이스 값을 함수 매개 변수로 전달하는 것과 동일한 버킷에 속할 수 있습니다.
수년에 걸쳐이 영역에서 몇 가지 버그가 있었던 것을 기억합니다. 오래 전에 (D3? D4?) 컴파일러는 중간 값을 전혀 참조하지 않았습니다. 대부분의 경우 작동했지만 매개 변수 별칭 상황에서 문제가 발생했습니다. 이 문제가 해결되면 const 매개 변수에 대한 후속 조치가있었습니다. 항상 중간 값 인터페이스의 처리를 필요한 명령문 이후 가능한 한 빨리 옮기고 싶은 욕구가 있었지만 컴파일러가 설정되지 않았기 때문에 Win32 최적화 프로그램에서 구현 된 적이 없다고 생각합니다. 문 또는 블록 세분화에서 처리를 처리합니다.
답변
컴파일러가 임시 보이지 않는 변수를 생성하지 않을 것이라고 보장 할 수 없습니다.
그리고 그렇게하더라도 최적화를 해제 (또는 스택 프레임?)하면 완벽하게 확인 된 코드가 엉망이 될 수 있습니다.
그리고 프로젝트 옵션의 가능한 모든 조합에서 코드를 검토하더라도 Lazarus 또는 새로운 Delphi 버전과 같은 코드를 컴파일하면 지옥으로 돌아올 것입니다.
가장 좋은 방법은 “내부 변수가 루틴보다 오래 지속될 수 없음”규칙을 사용하는 것입니다. 컴파일러가 내부 변수를 생성하는지 여부는 일반적으로 알 수 없지만 루틴이 존재할 때 그러한 변수 (만든 경우)가 최종화 될 것임을 알고 있습니다.
따라서 다음과 같은 코드가있는 경우 :
// 1. Some code which may (or may not) create invisible variables
// 2. Some code which requires release of reference-counted data
예 :
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;
그런 다음 “Work with interface”블록을 서브 루틴으로 래핑해야합니다.
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;
간단하지만 효과적인 규칙입니다.