I know this topic is a bit old, but it has been linked to quite often recently, and when I was looking at that I noticed that this could not really work.
So I did a small test:
procedure Smack;
var
arr: array[0..1023] of Byte;
begin
FillChar(arr, SizeOf(arr), $FF);
end;
procedure TestStackObject;
var
s: TStackObject;
begin
s := TStackObject.Create;
s.x := 42;
Smack;
s.show; // Segfault when virtual when non virtual prints -1 not 42
end;
As I suspected, it does not work. The problem is that _alloca is called from within the newInstance functions stack frame, so the memory is allocated on that stack frame. When newInstance returns the newly created instance, it's stackframe is poped and the memory is not available anymore.
In this example then the stack frame of Smack will be overlapping with the old stack frame of NewInstance, and accessing it's local variables accesses the memory where the class is located. Thats why the completely legal and in-bound FillChar on a local variable overrides the class info.
The only way to solve this is to alloca before NewInstance is used, and then be just used by NewInstance:
type
TStackObject = class
public
x: Integer;
class function newinstance : tobject;override;
procedure FreeInstance;override;
procedure show;
end;
threadvar
StackMemory: Pointer;
class function TStackObject.NewInstance:Tobject;
begin
if StackMemory <> nil then
InitInstance(StackMemory);
NewInstance:=TObject(StackMemory);
end;
procedure TStackObject.FreeInstance;
begin
CleanUpInstance;
// no free!
end;
procedure TStackObject.Show;
begin
writeln(self.x);
end;
procedure Smack;
var
arr: array[0..1023] of Byte;
begin
FillChar(arr, SizeOf(arr), $FF);
end;
procedure TestStackObject;
var
s: TStackObject;
begin
StackMemory := _alloca(TStackObject.InstanceSize, 4);
s := TStackObject.Create;
s.x := 42;
Smack;
s.show;
end;
Another alternative would be to use ASM to move the current stack frame up within NewInstance and thereby "allocate" memory on the previous stack frame.