[delphi] 암시 적 인터페이스 변수의 컴파일러 처리가 문서화되어 있습니까?

얼마 전에 암시 적 인터페이스 변수에 대해 비슷한 질문을했습니다 .

이 질문의 원인은 컴파일러에 의해 생성 된 암시 적 인터페이스 변수의 존재를 모르기 때문에 내 코드의 버그였습니다. 이 변수는 자신을 소유 한 프로 시저가 완료 될 때 완성되었습니다. 결과적으로 변수의 수명이 예상보다 길어 버그가 발생했습니다.

이제 컴파일러의 흥미로운 동작을 설명하는 간단한 프로젝트가 있습니다.

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;

간단하지만 효과적인 규칙입니다.


답변