Delphi XE a problém s generiky

vložil Igor Gottwald 2. února 2012 20:46

Dnes bych se s našimi čtenáři rád podělil o jednu nepříjemnou zkušenost z Delphi XE, související s použitím generických struktur a tříd.

Dobrá zpráva je, že v Delphi XE2 Update 3 se tato chyba již neprojevuje. Problém se týká případů, kdy nadefinujete generickou strukturu s více jak dvěma typy (TMyStruct<A,B> = record … end), velikost implementace není větší než 4 byty a voláte funkci, která má tuto strukturu vrátit jako svůj výsledek.

Zní to docela složitě, ale příklad to ozřejmí:

type
  TMyStruct<Type1,Type2> = record
    Value1: Type1;
    Value2: Type2;
  end;

  TTestClass<Type1,Type2> = class
  public
    function Get(AValue1: Type1; AValue2: Type2): TMyStruct<Type1,Type2>;
  end;

function TTestClass<Type1,Type2>.Get(AValue1: Type1; AValue2: Type2):
   TMyStruct<Type1,Type2>;
begin
  // zde nastane AV chyba
  Result.Value1 := AValue1;
  Result.Value2 := AValue2;
end;

function Test(A, B: Byte): Integer;
var
  ATestClass: TTestClass<Byte,Byte>;
  Value: TMyStruct<Byte,Byte>;
begin
  ATestClass := TTestClass<Byte,Byte>.Create;
  try
    // tak tohle se nepovede…
    Result := ATestClass.Get(A, B).Value1 + ATestClass.Get(A, B).Value2;
  finally
    ATestClass.Free;
  end;
end;

Zavoláním funkce TEST s libovolnými parametry zajistíte spolehlivé spuštění výjimky z důvodu porušení ochrany paměti.

Co se vlastně stane? Pokud se podíváte do zdrojového kódu vytvořeného kompilátorem, zjistíte, že metoda Get ukládá výsledek do paměti na předem alokované místo, které musí připravit volající metoda. Naproti tomu ale funkce Test, jelikož se výsledek volání vejde do 32 bitového registru EAX, očekává vrácení výsledku v registru a žádné místo v paměti nepřipraví. Důsledkem toho dojde k přepisu prakticky náhodného místa v operační paměti počítače. Naštěstí je to většinou 0x00000000 a pokud používáte procesor s kontrolou ochrany paměti, tak aplikace skončí AV chybou. V horším případě, který lze rovněž simulovat, ale dojde k přepisu libovolného jiného místa a důsledky mohou být nepředvídatelné.

Jistě si říkáte, že výše popsaný případ je velmi specifický. Nicméně má nepříjemný důsledek a tím je problematické použití generické třídy TDictionary<TKey,TValue>, která je jinak velmi užitečná. Pokud použijete pro TKey a TValue datové typy, jejichž velikost se vejde do 4 bytů, pak není možné použít konstrukci for APair in AnInstanceOfTheDictionary do … , jelikož vrácený datový typ TPair<TKey,TValue> odpovídá výše popsanému typu TMyStruct<Type1,Type2> a první průchod cyklem okamžitě vyvolá AV chybu nebo přepis paměti.

Při použití této konstrukce je tedy nutné mít na zřeteli, aby součet velikostí použitých datových typů byl alespoň 5 bytů anebo změnit cyklus tak, aby procházel klíče slovníku, tj. for AKey in AnInstanceOfTheDictionary.Keys do

Problematické jsou například implementace TDictionary<Byte,Byte>, TDictionary<Byte,Word> apod.

Naopak bez problémů jsou TDictionary<Byte,Integer>, TDictionary<string,Byte> apod.

Věřím, že příznivce Delphi tato ukázka neodradí od používání jinak velice výkonných generických tříd a případně pomůže těm z Vás, kteří se do podobné situace dostali a nedokázali přijít na to, proč jim aplikace padá nebo se nechová dle očekávání.

Igor Gottwald

Pozn. editora: rádi uveřejníme i Vaše příspěvky - nebojte se mi napsat.

Tagy: ,

Praxe

Komentování ukončeno

Naše nabídka

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy).

love Delphi

O Delphi.cz

Delphi je moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX, Linux a na iPhone a Android.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Poslední komentáře

Comment RSS

Dle měsíců