2

I am thinking about the New and Dispose commands from Delphi and was wondering if I can use these commands in other procedures/functions/threads, etc. within my process.

I would like to store the address to a TList but I am a little insecure since it uses the var reference which could be used to 'Save' the vars actual address. I don't want any access violations or anything... Here is my code:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    MyTList       : TList;
  public
    { Public declarations }
  end;

var
  Form1         : TForm1;

type
  TMyStruct = record
    Int1        : Integer;
    Int2        : Integer;
    Str1        : String;
    Str2        : String;
end;


implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  P_TMyStruct : ^TMyStruct;
  I           : Integer;
begin
  for I := 1 to 3 do begin
    New (P_TMyStruct);
    P_TMyStruct^.Int1 := I;
    P_TMyStruct^.Int2 := 1337;
    P_TMyStruct^.Str1 := inttostr(I);
    P_TMyStruct^.Str2 := '1337';
    MyTList.Add(P_TMyStruct);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  I : Integer;
begin
  for I := 0 to MyTList.Count - 1 do begin
    ShowMessage(inttostr(TMyStruct(MyTList.Items[i]^).Int1));
    ShowMessage(inttostr(TMyStruct(MyTList.Items[i]^).Int1));
    ShowMessage(TMyStruct(MyTList.Items[i]^).Str1);
    ShowMessage(TMyStruct(MyTList.Items[i]^).Str2);
    Dispose(MyTList.Items[i]);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyTList := TList.Create;
end;

end.

So would that be safe since I'm not disposing it in the stack?

Disillusioned
  • 14,635
  • 3
  • 43
  • 77
Ben
  • 3,380
  • 2
  • 44
  • 98
  • 1
    You're leaking memory by disposing only a pointer, what you should be disposing is a pointer to `TMyStruct` to let go of the strings. Given `PMyStruct=^TMyStruct`, you should call `Dispose(PMyStruct(MyTList.Items[i]))`. I don't understand your question about safety otherwise. – Sertac Akyuz Sep 12 '13 at 00:28
  • 1
    You're leaking memory, as Sertac said. Other than that, your question is very unclear. `New` and `Dispose` are memory allocation/deallocation procedures that are in the `System` unit, so of course they're globally available anywhere that uses that unit. Your question seems to be whether the memory blocks allocated are globally available, and the answer is no unless you make the `TList` holding those references globally available. If that's what you in fact mean, you should edit your question to ask that instead. – Ken White Sep 12 '13 at 00:32
  • What's your Delphi version? And what is the underlying problem you are trying to solve? – Jeroen Wiert Pluimers Sep 12 '13 at 06:45
  • 1
    I believe that you have misunderstood the OP. His problem clearly is that he does not know much about the underlying architecture, and not sure, he could free memory from a different method from where he allocated it. @Benjamin Weiss - feel free to release allocated memory wherever you wish to (of course if you are not using it anymore), because it is done by the OS not the Delphi framework. However the mentioned leak still exists. – mg30rg Sep 12 '13 at 07:24
  • @mg30rg That's what I primarily needed to know. Thank you for all your comments. – Ben Sep 12 '13 at 09:59
  • It seem to me most confusion was caused by the wording of the title. I've edited it, and hopefully the update addresses the problem. – Disillusioned Sep 12 '13 at 21:15
  • 1
    @BenjaminWeiss: If you knew what a Heap means, you would have not needed to ask this question. I think you should do some basic research and reading because there are probably lots of basic things that you will need to know to do native development in Windows. Any good big Delphi book would help you, like the big Marco Cantu tomes. – Warren P Sep 12 '13 at 21:23

1 Answers1

5

Dynamic memory allocations in Delphi will return memory on the heap and not the stack. (See Marco Cantu's explanation of these terms.) As such, the memory will be "globally accessible" in your application provided a reference to that memory is available. The New() procedure is one way to dynamically allocate memory. (Some others are: GetMem(), string assignments, object creation.)

So what you're proposing is safe (in terms of not causing access violations).
However, it will leak memory because the way you are using it, Dispose() will not deallocate all memory.

When you assign a string value to the struct, more memory is allocated on the heap. When you later Dispose your struct, the exact memory allocated in New() is deallocated, but at the low level, (simple Pointer reference to the memory), Delphi has no knowledge that there may be other internal structures that need deallocation; so the strings are leaked.

Fixing the memory leak

What you need to do is cast the pointer returned by MyTList.Items[i] to its correct type. But first you need to explicitly declare a type for the pointer to your struct. As a standard convention, most Delphi programmers will declare the pointer type with the same name, replacing the T prefix with P. I.e.

PMyStruct = ^TMyStruct;
TMyStruct = record
  Int1        : Integer;
  Int2        : Integer;
  Str1        : String;
  Str2        : String;
end;

Then when you do the following: Dispose(PMyStruct(MyTList.Items[i])), the compiler "recongnises" the type the pointer is referring to and will use that information to perform additional actions for its managed types. The point is the compiler can automatically handle the managed types correctly - but only if you give it the correct information about the struct that contains them.

The types affected by this are:

  • strings
  • dynamic arrays
  • interface references (which will need a ref released)
  • Also any indirect references to the above managed types will be managed. E.g.
  • If a Variant or OleVariant references and interface, it will be managed.
  • If a child record within a record (struct) references managed types, they will be managed.
  • arrays of strings, arrays of interfaces... each string/interface entry will also be managed.

Given that there are many more possible permutations to the above, it is safer to always ensure that Dispose() knows the type that was used in the initial New() allocation.

Non Managed Types

Perhaps given the above discussion, I should make a specific qualification about types that are not managed types. One can infer (and it would be accurate) that non managed types are not automatically deallocated when the containing structure is Dispose()d.

E.g. If your record structure contains an object reference, then because an object reference is not a managed type, you would still have to explicitly control the lifetime of that object instance. (Fortunately there are many techniques to do so.)

Community
  • 1
  • 1
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • This also matters for the variant types, `Variant` and `OleVariant`, and for procedure/function references (`reference to procedure`) it's not intuitive that they are implemented as interfaces, so it would help to point those out as well. –  Sep 12 '13 at 06:51
  • So should I free the strings (`Str1:='';`) before I call `Dispose`? – Ben Sep 12 '13 at 10:01
  • So I'm still not sure about it, since a user claims that `Dispose` will take care of the strings http://stackoverflow.com/questions/1202544/delphi-structures-strings-not-being-freed-fastmm-manager – Ben Sep 12 '13 at 10:10
  • 1
    @BenjaminWeiss No, you should call `Dispose` with a pointer of the correct type, like this answer indicates. Nothing on that other question contradicts this answer, that other question calls `Dispose` with a `PSomeStruct` argument, like this answer says you should do. :) –  Sep 12 '13 at 10:28
  • 1
    The point is that unless you tell the compiler information about the type, it does not know how to dispose of any managed types inside the type. – David Heffernan Sep 12 '13 at 11:44
  • 1
    Thanks for the feedback everyone, I've updated the answer accordingly to hopefully avoid future confusion. – Disillusioned Sep 12 '13 at 21:11