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


86

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

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

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

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 내부의 코드를 별도의 메서드로 추출하여 문제를 해결하여 인터페이스 변수의 범위를 좁혔습니다.


8
질문이 정말 복잡하다는 것 외에 왜 이것이 반대표를 받았는지 모르겠습니다. 내 머리를 넘어서서 찬성했습니다. 나는 정확히이 신비한 부분이 내가 1 년 전에 작업 한 앱에서 약간의 미묘한 참조 계산 버그를 초래했다는 것을 알고 있습니다. 최고의 괴짜 중 한 명이 그것을 알아내는 데 몇 시간을 보냈습니다. 결국 우리는이 문제를 해결했지만 컴파일러가 어떻게 작동하는지 이해하지 못했습니다.
Warren P

3
@Serg 컴파일러는 참조 계산을 완벽하게 수행했습니다. 문제는 내가 볼 수없는 참조를 보유하는 추가 변수가 있다는 것입니다. 내가 알고 싶은 것은 컴파일러가 그러한 여분의 숨겨진 참조를 취하도록 유도하는 것입니다.
David Heffernan 2011 년

3
이해 합니다만 이러한 추가 변수에 의존하지 않는 코드를 작성하는 것이 좋습니다. 컴파일러가 이러한 변수를 원하는만큼 생성하도록하세요. 견고한 코드는 이에 의존해서는 안됩니다.
kludg

2
이런 일이 발생하는 또 다른 예 :procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
Ondrej Kelle

2
나는 이것을 컴파일러 버그라고 부르고 싶다 ... 임시는 범위를 벗어난 후에 지워 져야 한다. 이것은 할당의 끝 이 되어야 한다 (함수의 끝이 아니라). 그렇게하지 않으면 발견 한 미묘한 오류가 발생합니다.
nneonneo

답변:


15

이 동작에 대한 문서가 있으면 함수 결과를 매개 변수로 전달할 때 중간 결과를 보유하는 임시 변수의 컴파일러 생성 영역에있을 것입니다. 이 코드를 고려하십시오.

procedure UseInterface(foo: IInterface);
begin
end;

procedure Test()
begin
    UseInterface(Create());
end;

컴파일러는 Create의 결과가 UseInterface로 전달 될 때 유지되는 암시 적 임시 변수를 만들어 인터페이스가 UseInterface 호출의 수명보다> = 수명을 갖도록해야합니다. 그 암시 적 임시 변수는 그것을 소유 한 프로 시저의 끝,이 경우 Test () 프로 시저의 끝에서 처리됩니다.

컴파일러가 값이 어디로 가는지 "볼"수 없기 때문에 포인터 할당 사례가 중간 인터페이스 값을 함수 매개 변수로 전달하는 것과 동일한 버킷에 속할 수 있습니다.

수년에 걸쳐이 영역에서 몇 가지 버그가 있었던 것을 기억합니다. 오래 전에 (D3? D4?) 컴파일러는 중간 값을 전혀 참조하지 않았습니다. 대부분의 경우 작동했지만 매개 변수 별칭 상황에서 문제가 발생했습니다. 이 문제가 해결되면 const 매개 변수에 대한 후속 조치가있었습니다. 항상 중간 값 인터페이스의 처리를 필요한 명령문 이후 가능한 한 빨리 옮기고 싶은 욕구가 있었지만 컴파일러가 설정되지 않았기 때문에 Win32 최적화 프로그램에서 구현 된 적이 없다고 생각합니다. 문 또는 블록 세분화에서 처리를 처리합니다.


0

컴파일러가 임시 보이지 않는 변수를 생성하지 않을 것이라고 보장 할 수 없습니다.

그리고 그렇게하더라도 최적화를 해제 (또는 스택 프레임?)하면 완벽하게 확인 된 코드가 엉망이 될 수 있습니다.

그리고 프로젝트 옵션의 가능한 모든 조합에서 코드를 검토하더라도 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;

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


내 시나리오에서 I : = CreateInterfaceFromLib (...)로 인해 암시 적 로컬이 발생했습니다. 그래서 당신이 제안하는 것은 도움이되지 않습니다. 어쨌든 나는 이미 질문에 대한 해결 방법을 명확하게 보여주었습니다. 하나는 함수 범위에 의해 제어되는 암시 적 지역의 수명을 기반으로합니다. 내 질문은 암묵적인 지역 주민으로 이어질 시나리오에 관한 것입니다.
David Heffernan 2015

내 요점은 이것이 처음에 묻는 잘못된 질문이라는 것입니다.
Alex

1
당신은 그 관점에 환영하지만 그것을 코멘트로 표현해야합니다. 질문의 해결 방법을 재현하려고 (실패한) 코드를 추가하는 것이 이상하게 보입니다.
David Heffernan 2015
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.