Is the compiler's relation to implicit interface variables documented?

I asked a similar question about implicit interface variables not so long ago.

The source of this question was an error in my code due to the fact that I did not know about the existence of an implicit interface variable created by the compiler. This variable was completed when the procedure in which it was completed. This, in turn, caused an error because the lifetime of the variable was longer than I expected.

Now I have a simple project to illustrate interesting compiler behavior:

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 compiles the way you imagine. The local variable I , the result of the function, is passed as an implicit var parameter to Create . StoreToLocal up for StoreToLocal results in a single IntfClear call. There are no surprises.

However, StoreViaPointerToLocal handled differently. The compiler creates an implicit local variable that goes to Create . When Create returns, assignment P^ is performed. This leaves the procedure with two local variables containing links to the interface. StoreViaPointerToLocal up for StoreViaPointerToLocal results in two IntfClear calls.

The compiled code for StoreViaPointerToLocal as follows:

 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 

I can guess why the compiler does this. When this can prove that assigning a result variable does not raise an exception (i.e. if the variable is local), then it directly uses the result variable. Otherwise, it uses an implicit local and copies the interface after the function returns, thereby ensuring that we will not leak the link in case of an exception.

But I can not find any allegations about this in the documentation. This is important because the lifetime of an interface is important, and as a programmer you need to be able to influence it sometimes.

So, does anyone know if there is any documentation on this behavior? If no one knows more about this? How instance fields are handled, I have not tested this yet. Of course, I could try everything for myself, but I am looking for a more formal expression and always prefer not to rely on implementation details developed by trial and error.

Update 1

To answer Remyโ€™s question, itโ€™s important for me when I needed to finish work on the object behind the interface before performing another finalization.

 begin AcquirePythonGIL; try PyObject := CreatePythonObject; try //do stuff with PyObject finally Finalize(PyObject); end; finally ReleasePythonGIL; end; end; 

As it is written, this is normal. But in real code, I had a second implicit local code that was finalized after the GIL was released and was hacked. I solved the problem by extracting the code inside the Acquire / Release GIL into a separate method and thereby narrowing the scope of the interface variable.

+83
interface delphi delphi-2010
Oct 13 '11 at 18:59
source share
2 answers

If there is any documentation on this behavior, there will probably be intermediate results in the creation of the compiler of temporary variables when passing the results of the function as parameters. Consider this code:

 procedure UseInterface(foo: IInterface); begin end; procedure Test() begin UseInterface(Create()); end; 

The compiler must create an implicit temporary variable to hold the Create result, as it is passed to UseInterface to make sure the interface has a lifetime> = lifetime of the call to UseInterface. This implicit temporary variable will be located at the end of the procedure that it owns, in this case at the end of the Test () procedure.

It is possible that your pointer assignment case may fall into the same bucket as intermediate interface values โ€‹โ€‹as function parameters, since the compiler cannot โ€œseeโ€ where this value goes.

I remember that in recent years there have been several mistakes in this area. Once upon a time (D3? D4?), The compiler did not refer to the average intermediate value at all. It worked most of the time, but ran into problems in parameter alias situations. After it has been considered, I consider that continuation was observed regarding const params. There was always a desire to transfer the removal of the interface of intermediate values โ€‹โ€‹before it was possible, after the statement in which it was necessary, but I do not think that it was ever implemented in the Win32 optimizer, because the compiler was simply not installed for processing Utilities when performing an operation or block detailing.

+13
Nov 11 '13 at 19:35
source share

You cannot guarantee that the compiler will not decide to create a temporary invisible variable.

And even if you do, disabled optimizations (or even stack frames?) Can ruin your well-tested code.

And even if you manage to view your code under all possible combinations of project parameters - compiling your code under something like Lazarus or even a new version of Delphi will return hell.

It would be best to use the rule "internal variables cannot survive the routine." Usually we do not know if the compiler will create some internal variables or not, but we know that any such variables (if they are created) will be completed when a regular program exists.

Therefore, if you have a code like this:

 // 1. Some code which may (or may not) create invisible variables // 2. Some code which requires release of reference-counted data 

eg:.

 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; 

Then you just have to put the "Work with Interface" block into the routine:

 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; 

This is a simple but effective rule.

0
Feb 17 '15 at 23:22
source share



All Articles