Delphi: What are the advantages of using System.New() instead of a local variable, other than just spare a tiny amount of memory?

StackOverflow https://stackoverflow.com/questions/14228746

  •  14-01-2022
  •  | 
  •  

Question

Let's go back to the basics. Frankly, I have never used New and Dispose functions before. However, after I read the New() documentation and the included examples on the Embarcadero Technologies's website and the Delphi Basics explanation of New(), it leaves questions in my head:

What are the advantages of using System.New() instead of a local variable, other than just spare a tiny amount of memory?

Common code examples for New() are more or less as follows:

  var
      pCustRec : ^TCustomer;
  begin
      New(pCustRec);
      pCustRec^.Name := 'Her indoors';
      pCustRec^.Age  := 55;
      Dispose(pCustRec);
  end;

In what circumstances is the above code more appropriate than the code below?

  var
      CustRec : TCustomer;
  begin
      CustRec.Name := 'Her indoors';
      CustRec.Age  := 55;
  end;
Was it helpful?

Solution

If you can use a local variable, do so. That's a rule with practically no exceptions. This results in the cleanest and most efficient code.

If you need to allocate on the heap, use dynamic arrays, GetMem or New. Use New when allocating a record.

Examples of being unable to use the stack include structures whose size are not known at compile time, or very large structures. But for records, which are the primary use case for New, these concerns seldom apply.

So, if you are faced with a choice of stack vs heap for a record, invariably the stack is the correct choice.

OTHER TIPS

From a different perspective:

Both can suffer from buffer overflow and can be exploited.

If a local variable overflows, you get stack corruption.

If a heap variable overflows, you get heap corruption.

Some say that stack corruptions are easier to exploit than heap corruptions, but that is not true in general.

Note there are various mechanisms in operating systems, processor architectures, libraries and languages that try to help preventing these kinds of exploits.

For instance there is DEP (Data Execution Prevention), ASLR (Address Space Layout Randomization) and more are mentioned at Wikipedia.

A local static variable reserves space on the limited stack. Allocated memory is located on the heap, which is basically all memory available.

As mentioned, the stack space is limited, so you should avoid large local variables and also large parameters which are passed by value (absence of var/const in the parameter declaration).

A word on memory usage:
1. Simple types (integer, char, string, double etc.) are located directly on the stack. The amount of bytes used can be determined by the sizeof(variable) function.
2. The same applies to record variables and arrays. 3. Pointers and Objects require 4/8 bytes.

Every object (that is, class instances) is always allocated on the heap.

Value structures (simple numerical types, records containing only those types) can be allocated on the heap.

Dynamic arrays and strings content are always allocated on the heap. Only the reference pointer can be allocated on the stack. If you write:

  function MyFunc;
  var s: string;
  ...

Here, 4/8 bytes are allocated on the stack, but the string content (the text characters) will always be allocated on the heap.

So using New()/Dispose() is of poor benefit. If it contains no reference-counted types, you may use GetMem()/FreeMem() instead, since there is no internal pointer to set to zero.

The main drawback of New() or Dispose() is that if an exception occur, you need to use a try...finally block:

  var
    pCustRec : ^TCustomer;
  begin
    New(pCustRec);
    try
      pCustRec^.Name := 'Her indoors';
      pCustRec^.Age  := 55;
    finally
      Dispose(pCustRec);
    end;
  end;

Whereas allocating on the stack let the compiler do it for you, in an hidden manner:

  var
    CustRec : TCustomer;
  begin // here a try... is generated
    CustRec.Name := 'Her indoors';
    CustRec.Age  := 55;
  end;  // here a finally + CustRec cleaning is generated

That's why I almost never use New()/Dispose(), but allocate on stack, or even better within a class. 2

The usual case for heap allocation is when the object must outlive the function that created it:

  1. It is being returned as a function result or via a var/out parameter, either directly or by returning some container.

  2. It's being stored in some object, struct or collection that is passed in or otherwise accessible inside the procedure (this includes being signaled/queued off to another thread).

In cases of limited stack space you might prefer allocation from the heap.
Ref.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top