2015-05-10 1 views
3

Я поиграл с примером smartpointer от Сергея Антонова, см .: http://blog.barrkel.com/2008/11/reference-counted-pointers-revisited.html (где-то в комментариях).Почему эта оптимизация smartpointer не работает?

SSCCE:

program TestSmartPointer; 
{$APPTYPE CONSOLE}  
uses 
    System.SysUtils; 

type 
TInjectType<T> = record 
public 
    VMT: pointer; 
    unknown: IInterface; 
    RefCount: integer; 
    AValue: T; 
end; 

TInject<T> = class 
public type 
    TInjectType = TInjectType<T>; 
    PInjectType = ^TInjectType; 
end; 

PInjectObjectType = TInject<TObject>.PInjectType; 


TSmartPointer<T: class> = class 
    class function Wrap(const AValue: T): TFunc<T>; static; 
end; 

function Trick_Release(const obj: PInjectObjectType): Integer; stdcall; forward; 
function Trick_AddRef(const obj: PInjectObjectType): Integer; stdcall; forward; 
function Invoke(var obj): TObject; forward; 

const 
    PSEUDO_VMT: array [0 .. 3] of pointer = (nil, @Trick_AddRef, @Trick_Release, @Invoke); 

function Trick_AddRef(const obj: PInjectObjectType): Integer; stdcall; 
begin 
    Result:= AtomicIncrement(Obj^.RefCount); 
end; 

function Trick_Release(const obj: PInjectObjectType): Integer; stdcall; 
begin 
    Result:= AtomicDecrement(Obj^.RefCount); 
    if Result = 0 then obj^.AValue.Free; 
end; 

function Invoke(const obj: PInjectObjectType): TObject; 
begin 
    Result:= obj^.AValue; 
end; 

class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>; 
var 
    h: TInjectType<T>; 
begin 
    h.RefCount:= 1; 
    pointer(h.unknown):= @h; 
    h.VMT:= @PSEUDO_VMT; 
    h.AValue:= AValue; 
    //Alternative A, this fails 
    Result:= TFunc<T>(@h); 
    Inc(h.RefCount);   
    ////Alternative B, this works 
    //Result:= function: T  
    // begin 
    // Result:= h.AValue; 
    // end; 
end; 

type 
    TTestObject = class(TObject) 
    procedure Test; 
    destructor Destroy; override; 
    end; 


{ TTestObject } 

procedure TTestObject.Test; 
begin 
    WriteLn('Test'); 
end; 

destructor TTestObject.Destroy; 
begin 
    WriteLn('Free'); 
    inherited; 
end; 

procedure Test; 
var 
    TestObject: TFunc<TTestObject>; 
begin 
    TestObject:= TSmartPointer<TTestObject>.Wrap(TTestObject.Create); 
    TestObject.Test; 
    ReadLn; //Works up to this point. 
    <<<--- generates a AV here. 
end; 

begin 
    WriteLn('Start'); 
    Test; 
    WriteLn('End'); 
    ReadLn; 
end. 

Барри Келли объясняет, что:

TFunc = ссылка на функцию: T;

прямо эквивалентен:

TFunc = интерфейс функции Invoke: T; конец;

за исключением того, что местоположения ссылочного типа метода назначаются с использованием значений функции, метода или анонимного метода.

Анонимные методы реализованы как интерфейсы, которые выглядят так же, как ссылка на метод, на скрытый класс. Захват местоположения реализуется как перемещение (для локальных пользователей) и копирование (для параметров) в поля скрытого класса. Любые обращения захваченных местоположений в основной процедуре преобразуются для доступа к полям скрытого класса; локальная переменная, называемая $ frame, указывает на экземпляр этого скрытого класса.

Цель
Я хочу, чтобы оптимизировать создание умного указателя.
Для этого я использую VMT и использую его для эмуляции интерфейса.

Если я определяю свою wrap функцию следующим образом:

class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>; 
var 
    h: TInjectType<T>; 
begin 
    pointer(h.unknown):= @h; 
    h.VMT:= @PSEUDO_VMT; 
    h.AValue:= AValue; 
    Result:= function: T  
    begin 
     Result:= h.AValue; 
    end; 
end; 

Все работает.

Если оптимизировать его:

class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>; 
var 
    h: TInjectType<T>; 
begin 
    h.RefCount:= 1; 
    pointer(h.unknown):= @h; 
    h.VMT:= @PSEUDO_VMT; 
    h.AValue:= AValue; 
    //Alternative A, this fails 
    Result:= TFunc<T>(@h); 
    Inc(h.RefCount);   
end; 

It почти работает, но дает AV как только функция вызова закрывается.

procedure Test; 
var 
    TestObject: TFunc<TTestObject>; 
begin 
    TestObject:= TSmartPointer<TTestObject>.Wrap(TTestObject.Create); 
    TestObject.Test; 
    ReadLn; 
    //Works up to this point. 
    <<<--- generates a AV here. 
end; 

Вы ожидали бы AV произойти в _Release, но это не так, ведь происходит до этого.

TestNewStringHelper.dpr.98: TestObject.Test; 
00419F0B 8B45FC   mov eax,[ebp-$04]   

Здесь EAX = 0018FF40

00419F0E 8B10    mov edx,[eax] 
00419F10 FF520C   call dword ptr [edx+$0c] 
00419F13 E82CFFFFFF  call TTestObject.Test 
TestNewStringHelper.dpr.100: end; 
00419F18 33C0    xor eax,eax 
00419F1A 5A    pop edx 
00419F1B 59    pop ecx 
00419F1C 59    pop ecx 
00419F1D 648910   mov fs:[eax],edx 
00419F20 68359F4100  push $00419f35 
00419F25 8D45FC   lea eax,[ebp-$04] 

Здесь EAX = 0018FF6C Очевидно, что это должно быть таким же, как и раньше. Тот факт, что это не является причиной AV, который должен следовать:

00419F28 E87BF6FEFF  call @IntfClear <<-- AV 

Вызов IntfClear AV, потому что он не может найти подходящую цель для _Release. IOW вызов никогда не достигает _Release, но переходит в неизвестность.

System.pas.36036: MOV  EDX,[EAX] 
004095A8 8B10    mov edx,[eax] 
System.pas.36037: TEST EDX,EDX 
004095AA 85D2    test edx,edx 
System.pas.36038: JE  @@1 
004095AC 740E    jz $004095bc 
System.pas.36039: MOV  DWORD PTR [EAX],0 
004095AE C70000000000  mov [eax],$00000000 
System.pas.36043: PUSH EAX 
004095B4 50    push eax 
System.pas.36044: PUSH EDX 
004095B5 52    push edx 
System.pas.36045: MOV  EAX,[EDX] 
004095B6 8B02    mov eax,[edx] 
System.pas.36046: CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release 
004095B8 FF5008   call dword ptr [eax+$08] <<-- AV here 

Почему это необходимо и что мне нужно для настройки, чтобы оптимизированная версия работала?

+3

Я перестал читать на «Я хочу, чтобы оптимизировать создание» –

ответ

2

Код, который работает, фиксирует локальную переменную h. Это означает, что его срок службы расширен. Компилятор делает это путем выделения переменной h в куче.

Ваш код не имеет захвата переменной. Это означает, что h выделяется в стеке, и его время жизни заканчивается при возврате функции. Таким образом, ваши последующие ссылки на h недействительны.

+0

умный указатель Я не последующие ссылки на 'h'. Ошибка происходит в коде выхода 'procedure Test'. Там есть только оператор 'end ;'. Поэтому проблема должна заключаться в том, что некоторые ожидания компилятора не выполняются, это не значит, что я ссылаюсь на переменную вне области видимости. – Johan

+0

@Johan Вы ссылаетесь на него позже. –

+0

?? Да, теперь я в замешательстве. Где после «процедуры тестирования»; var TestObject: TFunc ; begin TestObject: = TSmartPointer .Wrap (TTestObject.Create); TestObject.Test; ReadLn; // Выполняется до этого момента. <<< --- здесь генерируется AV. end; 'ссылка? – Johan

1

Расширяя @ ответ Давида:

Проблема
Я забыл/получил ошеломлены тем, что TFunc... действительно указатель.
Таким образом, назначение Result:= TFunc<T>(@h); возвращает указатель на локальную переменную из области видимости.
(знак @ - это мертвая распродажа, с которой мы имеем дело с указателями (пропустили тоже)).

Теперь, когда мы возвращаем указатель вне сферы видимости, AV-диски обязательно последуют рано или поздно.
В этом случае позднее было вызвано выполнение функции Test, но (скрытый) вызов _Release сбой.

Решение
Ответ переместить вещи в кучу и настроить _Release сделать очистку.

class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>; 
type 
    TS = TSmartPointer<T>; 
    PS = ^TS; 
var 
    p: PS; 
begin 
    P:= GetMemory(SizeOf(TS)); 
    p.RefCount:= 1; 
    pointer(p.unknown):= p; 
    p.VMT:= @PSEUDO_VMT; 
    p.AValue:= AValue; 
    pointer(Result):= pointer(TFunc<T>(p)); 
end; 

function Trick_Release(const obj: PInjectObjectType): Integer; stdcall; 
begin 
    Result:= AtomicDecrement(Obj^.RefCount); 
    WriteLn('Release '+IntToStr(Obj.RefCount)); 
    if Result = 0 then begin 
    obj^.AValue.Free; 
    FreeMem(obj); 
    end; 
end; 

Сейчас он работает:

enter image description here

+0

Это именно то, что говорит мой ответ. –

+0

Да, но ваш ответ объясняет, почему, а не как. Я просто включил его здесь для справок в будущем, должен ли я изменить ваш ответ - вместо этого вопрос? – Johan

+0

Я не знаю, что вы имеете в виду. Ваш вопрос спрашивает «почему». Когда у вас есть указатель, который устарел, кто заботится о том, что будет дальше. Я нахожу это довольно неприятным. –