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


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.