Örtülü arayüz değişkenlerinin derleyici uygulaması belgelenmiş mi?


86

Çok uzun zaman önce örtük arayüz değişkenleri hakkında benzer bir soru sordum .

Bu sorunun kaynağı, derleyici tarafından oluşturulan örtük bir arabirim değişkeninin varlığından haberdar olmamamdan dolayı kodumdaki bir hataydı. Bu değişken, kendisine ait olan prosedür bittiğinde son halini almıştır. Bu da değişkenin ömrünün beklediğimden daha uzun olması nedeniyle bir hataya neden oldu.

Şimdi, derleyicinin bazı ilginç davranışlarını göstermek için basit bir projem var:

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.

StoreToLocaltam tahmin edebileceğiniz gibi derlenmiştir. IFonksiyonun sonucu olan yerel değişken , örtük bir varparametre olarak iletilir Create. İçin düzenli yukarı StoreToLocaltek bir çağrı sonuçlarıIntfClear . Orada sürpriz yok.

Ancak StoreViaPointerToLocalfarklı muamele görür. Derleyici, aktardığı örtük bir yerel değişken oluşturur Create. Döndüğünde Create, atama P^gerçekleştirilir. Bu, rutini arayüze referansları tutan iki yerel değişkenle bırakır. StoreViaPointerToLocalİki çağrı ile sonuçların toparlanması IntfClear.

İçin derlenen kod StoreViaPointerToLocalşu şekildedir:

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 

Derleyicinin bunu neden yaptığını tahmin edebiliyorum. Sonuç değişkenine atamanın bir istisna oluşturmayacağını kanıtlayabildiğinde (yani, değişken yerel ise) o zaman sonuç değişkenini doğrudan kullanır. Aksi takdirde, örtük bir yerel kullanır ve işlev geri döndüğünde arayüzü kopyalar, böylece bir istisna durumunda referansı sızdırmamamızı sağlar.

Ancak belgelerde bununla ilgili herhangi bir ifade bulamıyorum. Önemlidir çünkü arayüz ömrü önemlidir ve bir programcı olarak ara sıra onu etkileyebilmeniz gerekir.

Peki, bu davranışla ilgili herhangi bir belge olup olmadığını bilen var mı? Kimsenin daha fazla bilgisi yoksa? Örnek alanlar nasıl işlenir, bunu henüz kontrol etmedim. Elbette hepsini kendim deneyebilirim ama daha resmi bir ifade arıyorum ve her zaman deneme yanılma yoluyla oluşturulan uygulama detaylarına güvenmekten kaçınmayı tercih ediyorum.

Güncelleme 1

Remy'nin sorusuna cevap vermek için, başka bir sonlandırma yapmadan önce arayüzün arkasındaki nesneyi sonlandırmam gerektiğinde benim için önemliydi.

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

Böyle yazıldığı gibi sorun değil. Ancak gerçek kodda, GIL serbest bırakıldıktan ve bombalandıktan sonra sonuçlandırılan ikinci bir örtük yerel yerim vardı. Sorunu, Acquire / Release GIL içindeki kodu ayrı bir metoda çıkararak çözdüm ve böylelikle arayüz değişkeninin kapsamını daralttım.


8
Bunun neden olumsuz oy verildiğini bilmiyorum, bunun dışında soru gerçekten karmaşık. Beni aştığı için oy verildi. Tam olarak bu arcanum parçasının, bir yıl önce üzerinde çalıştığım bir uygulamada bazı ince referans sayma hatalarına neden olduğunu biliyorum. En iyi ineklerimizden biri bunu çözmek için saatler harcadı. Sonunda bunun üzerinde çalıştık ama derleyicinin nasıl çalışacağını asla anlamadık.
Warren P

3
@Serg Derleyici referans sayımını mükemmel bir şekilde yaptı. Sorun, göremediğim bir referansı tutan fazladan bir değişken olmasıydı. Bilmek istediğim şey, derleyiciyi böylesine fazladan, gizli bir referans almaya iten şeydir.
David Heffernan

3
Sizi anlıyorum, ancak bu tür ekstra değişkenlere bağlı olmayan kod yazmak iyi bir uygulamadır. Derleyicinin bu değişkenleri istediği kadar yaratmasına izin verin, sağlam bir kod ona bağlı olmamalıdır.
kludg

2
Bunun olduğu başka bir örnek:procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
Ondrej Kelle

2
Bunu bir derleyici hatası olarak adlandırmaya meyilliyim ... geçiciler kapsam dışına çıktıktan sonra temizlenmelidir, bu da atamanın sonu olmalıdır (işlevin sonu değil). Bunu yapmamak, keşfettiğiniz gibi ince hatalar üretir.
nneonneo

Yanıtlar:


15

Bu davranışın herhangi bir dokümantasyonu varsa, muhtemelen fonksiyon sonuçlarını parametre olarak iletirken ara sonuçları tutmak için geçici değişkenlerin derleyici üretimi alanında olacaktır. Bu kodu düşünün:

procedure UseInterface(foo: IInterface);
begin
end;

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

Derleyicinin, Create'in sonucunu UseInterface'e geçirilirken tutmak için örtük bir temp değişkeni oluşturması gerekir, böylece arabirimin bir ömre sahip olduğundan emin olmak için> = UseInterface çağrısının ömrü. Bu örtük temp değişkeni, kendisine sahip olan yordamın sonunda, bu durumda Test () yordamının sonunda atılacaktır.

Derleyici değerin nereye gittiğini "göremediğinden", işaretçi atama durumunuzun işlev parametreleri olarak ara arabirim değerlerini iletmekle aynı gruba düşmesi mümkündür.

Yıllar boyunca bu bölgede birkaç böcek olduğunu hatırlıyorum. Uzun zaman önce (D3? D4?), Derleyici ara değeri hiç saymıyordu. Çoğu zaman işe yaradı, ancak parametre takma adı durumlarında sorun yaşadı. Bu bir kez ele alındığında, sabit parametrelerle ilgili bir takip yapıldığına inanıyorum. Ara değer arayüzünün elden çıkarılmasını, gerekli olduğu ifadeden sonra mümkün olan en kısa sürede taşıma arzusu her zaman vardı, ancak bunun Win32 optimize edicisinde hiç uygulandığını sanmıyorum çünkü derleyici ayarlanmadı. ifadede veya blok ayrıntı düzeyinde elden çıkarma işlemine hazır.


0

Derleyicinin geçici bir görünmez değişken oluşturmaya karar vermeyeceğini garanti edemezsiniz.

Ve yapsanız bile, kapatılmış optimizasyon (veya hatta yığın çerçeveleri?) Mükemmel şekilde kontrol edilen kodunuzu bozabilir.

Kodunuzu tüm olası proje seçenekleri kombinasyonları altında gözden geçirmeyi başarsanız bile - kodunuzu Lazarus veya hatta yeni Delphi sürümü gibi bir şey altında derlemek cehennemi geri getirecektir.

En iyi bahis, "dahili değişkenler rutini aşamaz" kuralını kullanmaktır. Derleyicinin bazı dahili değişkenler oluşturup oluşturmayacağını genellikle bilmiyoruz, ancak bu tür değişkenlerin (eğer yaratılırsa) rutin varken sonlandırılacağını biliyoruz.

Bu nedenle, böyle bir kodunuz varsa:

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

Örneğin:

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;

Ardından, "Arayüzle çalış" bloğunu alt programa sarmalısınız:

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;

Basit ama etkili bir kuraldır.


Benim senaryomda, ben: = CreateInterfaceFromLib (...) örtük bir yerel ile sonuçlanıyordu. Yani önerdiğiniz şey yardımcı olmayacak. Her durumda, soruda açıkça bir geçici çözüm gösterdim. İşlev kapsamı tarafından kontrol edilen örtük yerellerin yaşam süresine dayalı olanı. Sorum, örtük yerlilere yol açacak senaryolarla ilgiliydi.
David Heffernan

Demek istediğim, bunun ilk başta sorulması gereken yanlış bir soru olduğuydu.
Alex

1
Bu bakış açısına hoş geldiniz ama bunu bir yorum olarak ifade etmelisiniz. Sorunun geçici çözümlerini yeniden üretmeye çalışan (başarısız bir şekilde) kod eklemek bana garip geliyor.
David Heffernan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.