1

The two programs as shown below try to test whether a object is freed, using the techniques described here Bad reference to an object already freed .

The first program as shown below runs correctly if compiled under Delphi 7, but wrongly if compiled under Delphi XE and upper. That is to say, it outputs

 D7          DXE 
True        True
True        True 
True        False
True        True
False       True
False       False

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function ValidateObj(Obj: TObject): Pointer;
// see { Virtual method table entries } in System.pas
begin
  Result := Obj;
  if Assigned(Result) then
    try
      if Pointer(PPointer(Obj)^) <> Pointer(Pointer(Cardinal(PPointer(Obj)^) + Cardinal(vmtSelfPtr))^) then
        // object not valid anymore
        Result := nil;
    except
      Result := nil;
    end;
end;

function ValidateObj2(Obj: TObject): Pointer;
type
  PPVmt = ^PVmt;
  PVmt = ^TVmt;
  TVmt = record
    SelfPtr : TClass;
    Other   : array[0..17] of pointer;
  end;
var
  Vmt: PVmt;
begin
  Result := Obj;
  if Assigned(Result) then
    try
      Vmt := PVmt(Obj.ClassType);
      Dec(Vmt);
      if Obj.ClassType <> Vmt.SelfPtr then
        Result := nil;
    except
      Result := nil;
    end;
end;

var
   Obj: TObject;
begin
  Obj := TObject.Create;
  Writeln(BoolToStr(Assigned(Obj), True));
  Writeln(BoolToStr(Assigned(ValidateObj(Obj)), True));
  Writeln(BoolToStr(Assigned(ValidateObj2(Obj)), True));
  Obj.free;
  Writeln(BoolToStr(Assigned(Obj), True));
  Writeln(BoolToStr(Assigned(ValidateObj(Obj)), True));
  Writeln(BoolToStr(Assigned(ValidateObj2(Obj)), True));
  Readln;
end.

The second program, explicitly using FastMM4, as shown below runs wrongly when compiled under Delphi 7 or XE and upper. That is to say, it outputs

Expected      D7    DXE
  False     False  False
  True      True   True
  True      True   True 
  True      True   False
  True      False  False
  True      True   True
  False     True   True
  False     True   False

program Project2;

{$APPTYPE CONSOLE}

uses
  FastMM4,
  SysUtils;

function ValidateObj(Obj: TObject): Pointer;
// see { Virtual method table entries } in System.pas
begin
  Result := Obj;
  if Assigned(Result) then
    try
      if Pointer(PPointer(Obj)^) <> Pointer(Pointer(Cardinal(PPointer(Obj)^) + Cardinal(vmtSelfPtr))^) then
        // object not valid anymore
        Result := nil;
    except
      Result := nil;
    end;
end;

function ValidateObj2(Obj: TObject): Pointer;
type
  PPVmt = ^PVmt;
  PVmt = ^TVmt;
  TVmt = record
    SelfPtr : TClass;
    Other   : array[0..17] of pointer;
  end;
var
  Vmt: PVmt;
begin
  Result := Obj;
  if Assigned(Result) then
    try
      Vmt := PVmt(Obj.ClassType);
      Dec(Vmt);
      if Obj.ClassType <> Vmt.SelfPtr then
        Result := nil;
    except
      Result := nil;
    end;
end;

var
   Obj: TObject;
begin
  Obj := TObject.Create;        
  Writeln(BoolToStr(Obj is FastMM4.TFreedObject, True));
  Writeln(BoolToStr(Assigned(Obj), True));
  Writeln(BoolToStr(Assigned(ValidateObj(Obj)), True));
  Writeln(BoolToStr(Assigned(ValidateObj2(Obj)), True));
  Obj.free;                                
  Writeln(BoolToStr(Obj is FastMM4.TFreedObject, True));
  Writeln(BoolToStr(Assigned(Obj), True));
  Writeln(BoolToStr(Assigned(ValidateObj(Obj)), True));
  Writeln(BoolToStr(Assigned(ValidateObj2(Obj)), True));
  Readln;
end.

I am confused how the wrong behavior is caused, and wonder how to test whether a object is freed for Delphi 7 and Delphi XE and upper, especially when FastMM4 is used ?

Community
  • 1
  • 1
SOUser
  • 3,802
  • 5
  • 33
  • 63
  • 2
    Simple answer, you cannot. – David Heffernan Oct 24 '15 at 13:52
  • Every object you want to test, must be assigned nil when disposed of. – LU RD Oct 24 '15 at 13:55
  • You might have created a new object that re-uses that address. How can you tell it apart? – David Heffernan Oct 24 '15 at 14:22
  • @DavidHeffernan Thank you for your time. Why do you mark this question duplicate of that one ?? I do not see any direct relation (It seems to me that this question is more related to the SO post contained). Furthermore, the first program works correctly if compiled using Delphi 7 without FastMM4. – SOUser Oct 24 '15 at 14:32
  • @DavidHeffernan I am trying to test whether a object is freed immediately after I free it. – SOUser Oct 24 '15 at 14:35
  • 1
    You are right. I picked the wrong dupe this is a dupe of the question you linked to. Anyway, if you ask this question, your program is already broken. Better to fix it. Don't have references that you can't trust. – David Heffernan Oct 24 '15 at 14:36
  • If you freed the object, it is freed. What is there to test? – David Heffernan Oct 24 '15 at 14:36
  • 1
    It's not really a duplicate question, but, why on earth would someone write a function like that in the first place? If there are conditions in your code where you can get a variable that you don't know has been freed or not, it needs to be nilled when it is no longer pointing to a valid object instance. (Some will start a religious war over that.) There is simply no way to do what you are trying to do, reliably. And you shouldn't do it. – Brandon Staggs Oct 24 '15 at 14:42
  • I asked this question simply because the solution provided in the SO post contained only works in certain circumstances and does not work in others. I am therefore curious why. – SOUser Oct 24 '15 at 14:43
  • 1
    Nothing can be expected to do this. – David Heffernan Oct 24 '15 at 14:45
  • 1
    If the code is thus, that it is not clear if an object has been freed or not, the code should be revised. Code in which it is unclear is, IMO, pretty chaotic. Good code does not need any such checks. Why does the code not work? Because an invalid address does not look any different from a valid address. The only clearly distinguishable invalid address is nil. But pointers do not automatically drop to nil when the objects they reference are freed, especially if they do not own the object they point to. Situations like that should simply not be coded. – Rudy Velthuis Oct 24 '15 at 22:49

4 Answers4

5

In general, it is not possible to make a robust test that a pointer refers to an instance that has been freed or not. It is the job of the programmer to maintain control of the lifetime of your objects.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
2

There is no way to check whether the object is valid, but to compare its pointer to NIL. Disallow objects to have more than one pointer, otherwise if this object freed by one pointer, reference to the same object on the second pointer will cause Access Violation.

The North Star
  • 104
  • 1
  • 10
  • 1
    You can't always avoid having copies of the "master" reference (e.g. if you pass an object to a function, you already make a copy of the reference), but the lifetime of those should be limited and well controlled. – Rudy Velthuis Oct 24 '15 at 22:41
1

you can test a VCL object if it is freed/freeing or not with the following code:

if  (csFreeNotification in Self.ComponentState) 
or (csDestroying in Self.ComponentState)  then ... //Self is Freed or Freeing.

But you can not apply this method to ordinary pointers (non-VCL objects)

suatDMK 2
  • 11
  • 2
1

I had problems with this too, but I got around it by doing the following

First create a new variable directly under interface

unit Login_Sys;

interface
var
bisnotinmemory:boolean=true;

Then go to the class you would like to randomly check to see if its still in memory's constructor and destructor methods and do something like this

constructor TUserlogin.create;
begin
  bisnotinmemory:=False;

and

destructor TUserlogin.free;
begin
  bisnotinmemory:=true;

If you have to keep track of multiple object then you could always make the "bisnotinmemory" variable I used into an array.

unit Login_Sys;

interface
var
bisnotinmemory: array[0..1] of Boolean = (true, true);

Just remember to add something like "iOBjectID : integer" to the create methode of the class like say

constructor TUserlogin.create(iOBjectID : integer);
begin
  bisnotinmemory[iOBjectID]:=false;
  iPersonalID:=iOBjectID;

You could even declare a variable like "iPersonalID" under the "private" area of the object for use when the destructor method gets called.

destructor TUserlogin.free;
begin
  bisnotinmemory[iPersonalID]:=true;

I tested this with Delphi 2010

Hanro50
  • 11
  • 2
  • This is a great solution for many situations. The only problem comes when you did not create the class that might be freed. In this situations, I suppose you could create a wrapper class of some sort or manually change the variable value whenever you create and free something, but that's not a great solution. I wonder if there's a smarter way to assign some sort of watcher on a class along the lines of your answer. – dallin Oct 05 '22 at 14:09