2010-12-22 11 views
16

क्या ऑब्जेक्ट की कक्षा लागू होने वाले इंटरफ़ेस का कॉन्स्ट इंटरफ़ेस पैरामीटर वाले किसी विधि के ऑब्जेक्ट का नया उदाहरण पास करते समय संकलक संकेत/चेतावनी देना चाहिए?क्या कंपाइलर इन्सटेंस इंस्टेंस को सीधे कॉन्फ़िगर इंटरफ़ेस पैरामीटर के रूप में पास करते समय संकेत/चेतावनी देना चाहिए?

संपादित करें: इस मुद्दे को स्पष्ट करने के लिए पाठ्यक्रम का नमूना सरल है। लेकिन वास्तविक जीवन में यह और अधिक जटिल हो जाता है: क्या होगा यदि निर्माण और उपयोग कोड में है जो अलग है (विभिन्न इकाइयां, विभिन्न वर्ग, विभिन्न परियोजनाएं)? क्या होगा यदि यह विभिन्न लोगों द्वारा बनाए रखा जाता है? क्या होगा यदि एक गैर-कॉन्स्ट पैरामीटर एक कॉन्स बन जाता है, और सभी कॉलिंग कोड की जांच नहीं की जा सकती है (क्योंकि कोड बदलने वाले व्यक्ति को सभी कॉलिंग कोड तक पहुंच नहीं है)?

क्रैश नीचे की तरह कोड, और कारण खोजने के लिए बहुत मुश्किल है।

पहले लॉग:

1.Run begin 

1.RunLeakCrash 
2.RunLeakCrash begin 
    NewInstance 1 
    AfterConstruction 0 
    3.LeakCrash begin 
    _AddRef 1 
    4.Dump begin 
    4.Dump Reference=10394576 
    4.Dump end 
    _Release 0 
    _Release Destroy 
    BeforeDestruction 0 
    3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it 
    _AddRef 1 
    4.Dump begin 
    4.Dump Reference=10394576 
    4.Dump end 
    _Release 0 
    _Release Destroy 
    BeforeDestruction 0 
    3.LeakCrash end with exception 

1.Run end 
EInvalidPointer: Invalid pointer operation 

तो कोड है कि समय से पहले ही वस्तु उदाहरण एक अंतरफलक को लागू करने विज्ञप्ति:

//{$define all} 

program InterfaceConstParmetersAndPrematureFreeingProject; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils, 
    Windows, 
    MyInterfacedObjectUnit in '..\src\MyInterfacedObjectUnit.pas'; 

procedure Dump(Reference: IInterface); 
begin 
    Writeln(' 4.Dump begin'); 
    Writeln(' 4.Dump Reference=', Integer(PChar(Reference))); 
    Writeln(' 4.Dump end'); 
end; 

procedure LeakCrash(const Reference: IInterface); 
begin 
    Writeln(' 3.LeakCrash begin'); 
    try 
    Dump(Reference); // now we leak because the caller does not keep a reference to us 
    Writeln(' 3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it'); 
    Dump(Reference); // we might crash here 
    except 
    begin 
     Writeln(' 3.LeakCrash end with exception'); 
     raise; 
    end; 
    end; 
    Writeln(' 3.LeakCrash end'); 
end; 

procedure RunLeakCrash; 
begin 
    Writeln(' 2.RunLeakCrash begin'); 
    LeakCrash(TMyInterfacedObject.Create()); 
    Writeln(' 2.RunLeakCrash end'); 
end; 

procedure Run(); 
begin 
    try 
    Writeln('1.Run begin'); 

    Writeln(''); 
    Writeln('1.RunLeakCrash'); 
    RunLeakCrash(); 

    finally 
    Writeln(''); 
    Writeln('1.Run end'); 
    end; 
end; 

begin 
    try 
    Run(); 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
    Readln; 
end. 

EInvalidPointer ही Dump(Reference); को दूसरी कॉल के अंदर प्रकट होगा। कारण यह है कि संदर्भ प्रकट करने वाली अंतर्निहित वस्तु की संदर्भ संख्या पहले ही शून्य है, इसलिए अंतर्निहित वस्तु पहले ही नष्ट हो चुकी है।

डाला जाता है या संकलक द्वारा छोड़े गए संदर्भ गिनती कोड पर कुछ नोट:

  • मापदंडों const (procedure Dump(Reference: IInterface); में की तरह) के साथ चिह्नित नहीं अंतर्निहित ट्राई/अंत में ब्लॉक पाने के संदर्भ गिनती करने के लिए।
  • मापदंडों const (procedure LeakCrash(const Reference: IInterface); में की तरह) के साथ चिह्नित एक वस्तु उदाहरण सृष्टि के परिणाम गुजर किसी भी संदर्भ गिनती कोड
  • नहीं मिलता है (जैसे LeakCrash(TMyInterfacedObject.Create());) किसी भी संदर्भ गिनती कोड

अकेला के सभी उत्पन्न नहीं करता है उपरोक्त कंपाइलर व्यवहार बहुत तार्किक हैं, लेकिन संयुक्त वे एक EInvalidPointer का कारण बन सकते हैं।
EInvalidPointer केवल एक बहुत संकीर्ण उपयोग पैटर्न में प्रकट होता है।
पैटर्न को संकलक द्वारा पहचानना आसान है, लेकिन जब आप इसमें फंस जाते हैं तो डीबग करना या कारण ढूंढना बहुत मुश्किल है।
कामकाज बहुत सरल है: TMyInterfacedObject.Create() का परिणाम मध्यवर्ती चर में, फिर इसे LeakCrash() पर पास करें।

क्या संकलक इस उपयोग पैटर्न के बारे में आपको संकेत या चेतावनी देना चाहिए?

अंत में कोड मैं पता लगाने के लिए उपयोग किए गए सभी _AddRef/_Release/आदि कॉल:

unit MyInterfacedObjectUnit; 

interface 

type 
    // Adpoted copy of TInterfacedObject for debugging 
    TMyInterfacedObject = class(TObject, IInterface) 
    protected 
    FRefCount: Integer; 
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; 
    function _AddRef: Integer; stdcall; 
    function _Release: Integer; stdcall; 
    public 
    procedure AfterConstruction; override; 
    procedure BeforeDestruction; override; 
    class function NewInstance: TObject; override; 
    property RefCount: Integer read FRefCount; 
    end; 

implementation 

uses 
    Windows; 

procedure TMyInterfacedObject.AfterConstruction; 
begin 
    InterlockedDecrement(FRefCount); 
    Writeln('  AfterConstruction ', FRefCount); 
end; 

procedure TMyInterfacedObject.BeforeDestruction; 
begin 
    Writeln('  BeforeDestruction ', FRefCount); 
    if RefCount <> 0 then 
    System.Error(reInvalidPtr); 
end; 

class function TMyInterfacedObject.NewInstance: TObject; 
begin 
    Result := inherited NewInstance; 
    TMyInterfacedObject(Result).FRefCount := 1; 
    Writeln('  NewInstance ', TMyInterfacedObject(Result).FRefCount); 
end; 

function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 
    Writeln('  QueryInterface ', FRefCount); 
    if GetInterface(IID, Obj) then 
    Result := 0 
    else 
    Result := E_NOINTERFACE; 
end; 

function TMyInterfacedObject._AddRef: Integer; 
begin 
    Result := InterlockedIncrement(FRefCount); 
    Writeln('  _AddRef ', FRefCount); 
end; 

function TMyInterfacedObject._Release: Integer; 
begin 
    Result := InterlockedDecrement(FRefCount); 
    Writeln('  _Release ', FRefCount); 
    if Result = 0 then 
    begin 
    Writeln('  _Release Destroy'); 
    Destroy; 
    end; 
end; 

end. 

--jeroen

उत्तर

20

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

+0

ठीक है; यह क्यूसी होगा। –

+1

मैं अब 10 वर्षों से इस पर काम कर रहा हूं। मुझे विश्वास नहीं है कि यह मुद्दा पहले से ही ज्ञात नहीं था और इसलिए माना जाता है कि यह डिजाइन/ठीक नहीं होगा। आज इसके बारे में सोचते हुए यह स्पष्ट प्रतीत होता है कि इसे ठीक किया जा सकता है क्योंकि यह अन्य प्रबंधित प्रकारों (स्ट्रिंग्स, डायन एरे, वेरिएंट इत्यादि) के लिए नहीं होता है। –

+0

@ बैरी: http://qc.embarcadero.com/wc/qcmain। एएसपीएक्स? डी = 90482 –

3

मैं एक चेतावनी के लिए वोट चाहते हैं, के रूप में भी अनुभवी डेवलपर्स इस में गिर सकता है जाल। अगर कोई भी चेतावनी नहीं चाहता है तो इसे आसानी से अक्षम किया जा सकता है, जिसका अर्थ है वर्तमान व्यवहार के संबंध में कोई बदलाव नहीं। यह कुछ हद तक प्रारंभिक चर या तो चेतावनियों की तरह है।

इस समस्या का एक और समाधान कॉन्स इंटरफेस पैरामीटर के लिए एक अंतर्निहित Assert(InterfaceParameter.RefCount > 0); हो सकता है। संभवतया केवल उत्सर्जित होने पर ही उत्सर्जित होता है।

5

एक वस्तु उदाहरण सृष्टि के परिणाम (जैसे LeakCrash (TMyInterfacedObject.Create());) गुजर किसी भी संदर्भ गिनती कोड

उत्पन्न नहीं करता है ऊपर संकलक बग है। प्रक्रिया में मौजूद होने पर इसे एक छिपी हुई विविधता और काउंटर को कम करना होगा

संबंधित मुद्दे

 संबंधित मुद्दे