我一直在研究Sergey Antonov的smartpointer示例,请参见:http ://blog.barrkel.com/2008/11/reference-counted-pointers-revisited.html (在注释中的某个位置)。
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.
巴里·凯利(Barry Kelly)解释说:
TFunc =对函数的引用:T;
直接等于:
TFunc =接口函数调用: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;
它几乎可以工作,但是在调用函数关闭时立即给出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
为什么要这样做,我需要进行哪些调整才能使优化版本正常工作?
起作用的代码捕获局部变量h
。这意味着其寿命得以延长。编译器通过h
在堆上分配变量来做到这一点。
您的代码没有任何变量捕获。这意味着h
在函数返回时会在堆栈上分配它,并且其生存期结束。因此,您随后的引用h
无效。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句