निहित इंटरफ़ेस चर का संकलक उपचार प्रलेखित है?


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पैरामीटर के रूप में पारित किया जाता है CreateStoreToLocalएक कॉल में परिणाम के लिए साफ 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 

मैं अनुमान लगा सकता हूं कि कंपाइलर ऐसा क्यों कर रहा है। जब यह साबित हो सकता है कि परिणाम चर को निर्दिष्ट करना अपवाद नहीं बढ़ाएगा (अर्थात यदि चर स्थानीय है) तो यह परिणाम चर का सीधा उपयोग करता है। अन्यथा यह एक अंतर्निहित स्थानीय का उपयोग करता है और फ़ंक्शन को वापस करने के बाद इंटरफ़ेस को कॉपी करता है, इस प्रकार यह सुनिश्चित करता है कि हम अपवाद के मामले में संदर्भ को लीक नहीं करते हैं।

लेकिन मैं प्रलेखन में इसके बारे में कोई बयान नहीं पा सकता हूं। यह मायने रखता है क्योंकि इंटरफ़ेस जीवनकाल महत्वपूर्ण है और एक प्रोग्रामर के रूप में आपको इस अवसर पर प्रभावित करने में सक्षम होने की आवश्यकता है।

तो, क्या कोई यह जानता है कि क्या इस व्यवहार का कोई दस्तावेज है? यदि किसी को इसके बारे में अधिक ज्ञान नहीं है? उदाहरण क्षेत्र कैसे संभाले जाते हैं, मैंने अभी तक इसकी जाँच नहीं की है। बेशक, मैं इसे अपने लिए पूरी तरह से आज़मा सकता था, लेकिन मैं एक अधिक औपचारिक वक्तव्य की तलाश कर रहा हूं और हमेशा परीक्षण और त्रुटि द्वारा किए गए कार्यान्वयन विवरण पर निर्भर रहने से बचना पसंद करता हूं।

अपडेट १

रेमी के सवाल का जवाब देने के लिए, यह मेरे लिए मायने रखता है जब मुझे एक और अंतिम रूप देने से पहले इंटरफ़ेस के पीछे की वस्तु को अंतिम रूप देने की आवश्यकता होती है।

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

जैसा लिखा है यह ठीक है। लेकिन वास्तविक कोड में मेरे पास दूसरा निहित स्थानीय था जिसे जीआईएल जारी होने के बाद अंतिम रूप दिया गया था और उस पर बमबारी की गई थी। मैंने एक्वायर्ड / रिलीज़ GIL के अंदर कोड को एक अलग विधि में निकालकर समस्या को हल किया और इस तरह इंटरफ़ेस चर के दायरे को कम कर दिया।


8
पता नहीं क्यों इसे अस्वीकार कर दिया गया था, इसके अलावा यह सवाल वास्तव में जटिल है। मेरे सिर पर रास्ता होने के कारण ऊपर चढ़ा। मुझे पता है कि ठीक इसी तरह से एक वर्ष पहले मैंने जिस ऐप पर काम किया था, उसमें कुछ सूक्ष्म संदर्भ गिनती के कीड़े थे। हमारे सबसे अच्छे गीकों में से एक ने इसे लगाने में घंटों बिताए। अंत में हमने इसके चारों ओर काम किया लेकिन यह कभी नहीं समझा कि संकलक के काम करने का इरादा कैसा था।
वॉरेन पी

3
@Serg कंपाइलर ने अपनी संदर्भ गणना पूरी तरह से की। समस्या यह थी कि एक अतिरिक्त चर एक संदर्भ था जिसे मैं नहीं देख सकता था। मैं जानना चाहता हूं कि संकलक को इस तरह के एक अतिरिक्त, छिपे हुए, संदर्भ को लेने के लिए क्या उकसाता है।
डेविड हेफर्नन

3
मैं आपको समझता हूं, लेकिन एक अच्छा अभ्यास कोड लिखना है जो इस तरह के अतिरिक्त चर पर निर्भर नहीं करता है। कंपाइलर इन वैरिएबल को उतना ही बनाते हैं जितना इसे पसंद करते हैं, एक ठोस कोड को इस पर निर्भर नहीं होना चाहिए।
21

2
एक और उदाहरण जब यह हो रहा है:procedure StoreViaAbsoluteToLocal; var I: IInterface; I2: IInterface absolute I; begin I2 := Create; end;
ओन्ड्रेज केले

2
मुझे इसे कंपाइलर बग कहने का प्रलोभन है ... कार्यक्षेत्र से बाहर जाने के बाद अस्थायी रूप से साफ किया जाना चाहिए , जो असाइनमेंट का अंत होना चाहिए (और फ़ंक्शन का अंत नहीं)। ऐसा न करना सूक्ष्म त्रुटियों को उत्पन्न करता है जैसा कि आपने खोजा है।
nnonneo

जवाबों:


15

यदि इस व्यवहार का कोई दस्तावेज़ीकरण होता है, तो यह संभवतया अस्थायी चर के संकलक उत्पादन के क्षेत्र में होगा जब फ़ंक्शन परिणाम को मापदंडों के रूप में पारित करने के लिए मध्यवर्ती परिणाम धारण करेगा। इस कोड पर विचार करें:

procedure UseInterface(foo: IInterface);
begin
end;

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

कंपाइलर को Create के परिणाम को होल्ड करने के लिए एक अंतर्निहित अस्थायी वैरिएबल बनाना होता है क्योंकि यह UseInterface में पास होता है, यह सुनिश्चित करने के लिए कि इंटरफ़ेस में एक जीवनकाल> = UseInterface कॉल का जीवनकाल है। उस निहित अस्थायी चर को इस प्रक्रिया के अंत में निपटाया जाएगा, इस मामले में टेस्ट () प्रक्रिया के अंत में।

यह संभव है कि आपका पॉइंटर असाइनमेंट केस फंक्शन पैरामीटर्स के रूप में इंटरमीडिएट इंटरफेस वैल्यू के रूप में एक ही बकेट में गिर सकता है, क्योंकि कंपाइलर "देख" नहीं सकता है कि वैल्यू कहां जा रही है।

मुझे याद है कि इस क्षेत्र में पिछले कुछ वर्षों में कुछ बग आए हैं। बहुत पहले (D3? D4?), संकलक ने मध्यवर्ती मान को बिल्कुल नहीं गिना था। इसने अधिकांश समय काम किया, लेकिन पैरामीटर उर्फ ​​स्थितियों में परेशानी में पड़ गया। एक बार जब मुझे संबोधित किया गया तो कांस्टेबल के बारे में कहा गया था, मुझे विश्वास है। हमेशा यह इच्छा थी कि बयान के बाद इंटरमीडिएट वैल्यू इंटरफेस का निपटान जल्द से जल्द हो, जिसमें इसकी जरूरत थी, लेकिन मुझे नहीं लगता है कि कभी भी Win32 ऑप्टिमाइज़र में लागू हुआ क्योंकि कंपाइलर अभी सेट नहीं हुआ था स्टेटमेंट या ब्लॉक ग्रैन्युलैरिटी पर निपटान से निपटने के लिए।


0

आप यह गारंटी नहीं दे सकते कि संकलक एक अस्थायी अदृश्य चर बनाने का फैसला नहीं करेगा।

और यहां तक ​​कि अगर आप करते हैं, तो बंद अनुकूलन (या यहां तक ​​कि स्टैक फ्रेम?) आपके पूरी तरह से जाँच किए गए कोड को गड़बड़ कर सकता है।

और यहां तक ​​कि अगर आप परियोजना विकल्पों के सभी संभावित संयोजनों के तहत अपने कोड की समीक्षा करने का प्रबंधन करते हैं - अपने कोड को लाजर या यहां तक ​​कि नए डेल्फी संस्करण के तहत अपने कोड को संकलित करने से नरक वापस आ जाएगा।

एक सबसे अच्छा शर्त "आंतरिक चर को नियमित नहीं कर सकते हैं" नियम का उपयोग करना होगा। हम आम तौर पर नहीं जानते हैं, अगर कंपाइलर कुछ आंतरिक चर बनाएगा या नहीं, लेकिन हम जानते हैं, कि रूटीन मौजूद होने पर ऐसे किसी भी वैरिएबल (यदि बनाए गए) को अंतिम रूप दिया जाएगा।

इसलिए, यदि आपके पास इस तरह का कोड है:

// 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;

फिर आपको बस "इंटरफ़ेस के साथ काम करें" को उप-खंड में लपेटना चाहिए:

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 (...) का परिणाम एक निहित स्थानीय था। तो आप जो सुझाव देंगे वह मदद नहीं करेगा। किसी भी मामले में, मैंने पहले ही स्पष्ट रूप से प्रश्न में वर्कअराउंड का प्रदर्शन किया। फ़ंक्शन स्कोप द्वारा नियंत्रित होने वाले अंतर्निहित स्थानीय लोगों के जीवनकाल पर आधारित है। मेरा प्रश्न उन परिदृश्यों से संबंधित है जो निहित स्थानीय लोगों की ओर ले जाते हैं।
डेविड हेफर्नन

मेरा कहना यह था कि पहली जगह में पूछना एक गलत सवाल है।
एलेक्स

1
उस दृश्य बिंदु पर आपका स्वागत है लेकिन आपको इसे टिप्पणी के रूप में व्यक्त करना चाहिए। प्रश्न के वर्कअराउंड को पुन: पेश करने का प्रयास (असफल) करने वाला कोड जोड़ना मुझे अजीब लगता है।
डेविड हेफर्नन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.