为什么智能指针的这种优化不起作用?
Why does this optimization of a smartpointer not work?
我一直在研究 Sergey Antonov 的智能指针示例,请参阅: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 = reference to function: T;
is directly equivalent to:
TFunc = interface
function Invoke: T;
end;
except that locations of a method reference type are assignable using values of a function, method or anonymous method.
Anonymous methods are implemented as interfaces that look just like the method reference, on a hidden class. Location capture is implemented as moving (for locals) and copying (for parameters) to fields on the hidden class. Any accesses of captured locations in the body of the main procedure are converted to access the fields on the hidden class; a local variable, called $frame, points to an instance of this hidden class.
目标
我想优化智能指针的创建。
为此,我手工制作了 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-]
这里EAX = 0018FF40
00419F0E 8B10 mov edx,[eax]
00419F10 FF520C call dword ptr [edx+[=15=]c]
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 [=15=]419f35
00419F25 8D45FC lea eax,[ebp-]
这里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 [=17=]4095bc
System.pas.36039: MOV DWORD PTR [EAX],0
004095AE C70000000000 mov [eax],[=17=]000000
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+] <<-- AV here
为什么会这样?我需要调整什么才能使优化版本正常工作?
有效的代码捕获局部变量h
。这意味着它的寿命延长了。编译器通过在堆上分配变量 h
来做到这一点。
您的代码没有任何变量捕获。这意味着 h
被分配在堆栈上,其生命周期在函数 returns 时结束。因此,您随后对 h
的引用无效。
扩展@David 的回答:
问题
我 forgot/got 被 TFunc...
实际上是一个指针这一事实吓了一跳。
所以赋值 Result:= TFunc<T>(@h);
return 是指向超出范围的局部变量的指针。
(@
标志是我们正在处理指针的死赠品( 也错过了 ))。
现在我们 return 一个超出范围的指针,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;
现在可以使用了:
我一直在研究 Sergey Antonov 的智能指针示例,请参阅: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 = reference to function: T;
is directly equivalent to:
TFunc = interface function Invoke: T; end;
except that locations of a method reference type are assignable using values of a function, method or anonymous method.
Anonymous methods are implemented as interfaces that look just like the method reference, on a hidden class. Location capture is implemented as moving (for locals) and copying (for parameters) to fields on the hidden class. Any accesses of captured locations in the body of the main procedure are converted to access the fields on the hidden class; a local variable, called $frame, points to an instance of this hidden class.
目标
我想优化智能指针的创建。
为此,我手工制作了 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-]
这里EAX = 0018FF40
00419F0E 8B10 mov edx,[eax]
00419F10 FF520C call dword ptr [edx+[=15=]c]
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 [=15=]419f35
00419F25 8D45FC lea eax,[ebp-]
这里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 [=17=]4095bc
System.pas.36039: MOV DWORD PTR [EAX],0
004095AE C70000000000 mov [eax],[=17=]000000
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+] <<-- AV here
为什么会这样?我需要调整什么才能使优化版本正常工作?
有效的代码捕获局部变量h
。这意味着它的寿命延长了。编译器通过在堆上分配变量 h
来做到这一点。
您的代码没有任何变量捕获。这意味着 h
被分配在堆栈上,其生命周期在函数 returns 时结束。因此,您随后对 h
的引用无效。
扩展@David 的回答:
问题
我 forgot/got 被 TFunc...
实际上是一个指针这一事实吓了一跳。
所以赋值 Result:= TFunc<T>(@h);
return 是指向超出范围的局部变量的指针。
(@
标志是我们正在处理指针的死赠品( 也错过了 ))。
现在我们 return 一个超出范围的指针,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;
现在可以使用了: