Rucia
Rucia

Reputation: 249

GetMem, Pointers and Access Violations

I'm struggling with some memory allocation and a frustratingly difficult to locate and fix exception whilst using XE2.

I have a factory class which creates and stores dynamically created objects for display on a form, but the factory creation fails in some cases. If i execute the following code as the first action after program launch then all is well - the memory is allocated nicely and all other functions work. However, if i say open another form, or an open file dialog and then execute the factory creation there is an exception:

Exception EAccessViolation: Access violation at address 0040AA1F in module
'UMTester.exe'. Write of address 00000069 (OS Exception)  Exception occurred at
$0040AA1F (Module "System", Procedure "@DynArrayAsg", Unit "", Line 0)

The exception is always at the address 0040AA1F and the proc is always @DynArrayAsg.

Although the factory will be created instantly at launch i don't want to ignore the bug as that would just be bad form!

Here's the factory creation code:

constructor TFactory.Create(FactoryObjectClass : tclass;Capacity : integer);
var nn : integer;
fptr : pointer;
fObj : TFactoryObject;
begin
  fClass := FactoryObjectClass;
  fsize := fclass.InstanceSize;
  fdata:=nil;

  //Capacity represents the number of objects; trying to allocate small amounts 
  //of memory results in exceptions so i artificially increase it anything less 
  //than 20 throws an exception
  if Capacity<19 then Capacity:=20;

  //This seems to reduce exceptions as the requested memory is in whole blocks 
  while frac((Capacity*SizeOf(Pointer))/4)<>0.0 do Inc(Capacity);

  fcapacity := Capacity;

  //Allocate memory
  getmem(fdata,fsize*fcapacity);
  getmem(fDatalist,sizeof(Pointer)*fcapacity);
  getmem(ffreelist,sizeof(Pointer)*fcapacity);

  fdatacount :=0;
  ffreecount :=0;
  fptr       := fdata;

  //create a pointer to the factory object at each memory address
  for nn := 0 to Capacity-1 do begin
   fdatalist[fdatacount]:= fptr;         <------- Exception is always here
   fobj := Fclass.InitInstance(fptr) as TFactoryObject;
   fobj.factory := self;
   fObj.Create;
   fptr := pointer(integer(fptr)+fsize);
   inc(fDataCount);
  end;
end;

The factories pointer list is instantiated from a TDictionary with basic form elements like labels, memo's and listboxes etc.

  procedure TObjs.Initialize(AObjs : TDict);
  var
   nn :integer;
   C : TRttiContext;
   T : TRttiInstanceType;
   V : TValue;
  begin
    //Process all objects requested
    for nn := 0 to AObjs.Count-1 do begin
      //Locate the class from the supplied reference in AObjs
      T := (C.GetType(TClass(FindClass(AObjs[nn]['Type']))) as TRttiInstanceType);

      //Invoke the creation of the object by calling its native constructor
      V := T.GetMethod('Create').Invoke(T.metaClassType,[Application]);

      //Adds each requested object to the repository and displays the object
      Objs[nn].SetValue(V.AsObject,AObjs[nn]);
    end;
  end;

Observations and Thoughts:

  1. Perhaps heap fragmentation means the requested memory of fDatalist means i can't get the whole amount in a contiguous block.
  2. When creating the factory as the first action after pgm launch fDataList is always at address $1A9E2B0. If i show another form first, then create the factory fDataList is created at $1A9DF98. Some actions do not affect the address location, and no exception is ever thrown when fDataList is at address $1A9E2B0. Nowhere am i assigning fDataList to a specific address, so heaven knows why it works at this address and not others.
  3. On the line "if Capacity<19 then Capacity:=20;" if i change the 20 to 60 i can postpone the exception for longer, i.e i can execute an increased number of un-related procedures before creating the factory. The exception always occurs though after a certain amount of usage.

The factory is created once here:

  SetLength(Objs,AObjs.Count);
  AFactory := TFactory.Create(TchObj,AObjs.Count);

  //Initialize the full array of objects
  for Index := 0 to AObjs.Count -1 do
    Objs[Index] := Afactory.Request_obj as TchObj;

Class definition for TFactory is:

  TchFactory = class
    private
      fdata      : pointer;
      fsize      : integer;
      fDataList  : PPointerList;  // from classes unit
      fdatacount : integer;
      fFreeList  : PPointerList;
      fFreeCount : integer;
      fCapacity  : integer;
      fcLass     : TClass;
    public
      constructor Create(FactoryObjectClass : Tclass;Capacity : integer);
      destructor  Destroy;   override;
      function    Request_Obj : TchFactoryObject;
      procedure   Recycle(FactoryObject : TchFactoryObject);
    property Capacity  : integer read fCapacity;
    property CountUsed : integer read fdataCount;
    property CountFree : integer read fFreeCount;
  end;

What can i look at?
Any ideas on how i debug this?
Or am i just doing something insanely wrong?

EDIT: Removing the "trial and error" lines David refers to and changing GetMem to AllocMem fixes the issue. So the final code for the factory constructor is:

constructor TchFactory.Create(FactoryObjectClass : tclass;Capacity : integer);
  var Index : integer;
  fptr : pointer;
  fObj : TchFactoryObject;
  begin
    fClass := FactoryObjectClass;
    fsize := fclass.InstanceSize;

    fcapacity := Capacity;
    fdata := AllocMem(fsize*fcapacity);
    fdatalist:= AllocMem(sizeof(Pointer)*fcapacity);
    fFreelist:= AllocMem(sizeof(Pointer)*fcapacity);
    fdatacount :=0;
    ffreecount :=0;
    fptr       := fdata;

    for index := 0 to Capacity-1 do begin

      fdatalist[fdatacount]:= fptr;
      fobj := Fclass.InitInstance(fptr) as TchFactoryObject;
      fobj.factory := self;
      fObj.Create;
      fptr := pointer(integer(fptr)+fsize);
      inc(fDataCount);

     end;      
  end;

Upvotes: 0

Views: 1777

Answers (2)

David Heffernan
David Heffernan

Reputation: 613441

Am I just doing something insanely wrong?

Yes. Insanely wrong. To instantiate an instance you should simply call the constructor. You want to create a bunch of them at once? Run a loop and call the constructor inside that loop.


It is possible to instantiate objects that are contiguous in memory, but it takes skill and understanding to do so. And it involves relying on implementation details. You should only attempt to do that if there are sound performance reasons for doing so. From what I can see that is not the case. And so a simple for loop to instantiate instances in the usual way is what you need.

Upvotes: 3

Marco van de Voort
Marco van de Voort

Reputation: 26361

Afaik this is allowed, and without full declaration it is hard to see what goes wrong. But IIRC you must make sure your memory is zero-initialized.

If the class has automated types then that is required.

Upvotes: 0

Related Questions