Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi Access Violation Error When Assigning Strings between record types

I have a simple record type. I allocate an new instance of this record and use a procedure ("_clone") to copy values from an existing record to the new one. I obtain an access violation only when assigin a string value.

Any ideas? Help is much appreciated.


TYPE Definition:

TPointer = ^TAccessoryItem;
TAccessoryItem = Record
  Id : Integer;
  PartNumber : String;
  Qty : Integer;
  Description : String;
  Previous : Pointer;
  Next : Pointer;
end;

Procedure TAccessoryList._clone (Var copy : TAccessoryItem; Var original : TAccessoryItem);

 begin

    copy.Id := original.Id;
    copy.Qty := original.Qty;
    copy.Partnumber := original.Partnumber;  **// Access errors happens here**
    copy.Next := Nil;
    copy.Previous := Nil;

  end;

Calling application below:

  procedure TAccessoryList.AddItem(Var Item : TAccessoryItem);

 Var

    newItem : ptrAccessoryItem;

 begin

    GetMem(newItem, sizeOf(TAccessoryItem));

    _clone(newItem^, Item);

 end;
like image 234
mad moe Avatar asked Feb 26 '11 18:02

mad moe


2 Answers

You need to initialize your new structure with zeros. GetMem does not zero the allocated memory, so the fields of your record initially contain random garbage. You need to call

FillChar(newItem^, sizeof(TAccessoryItem), 0)

after the GetMem, before using the record.

Here's why: When you assign to the string field of the newly allocated but uninitialized record, the RTL sees that the destination string field is not null (contains a garbage pointer) and attempts to dereference the string to decrement its ref count before assigning the new string value to the field. This is necessary on every assignment to a string field or variable so that the previous string value will be freed if nothing else is using it before a new value is assigned to the string field or variable.

Since the pointer is garbage, you get an access violation... if you're lucky. It is possible that the random garbage pointer could coincidentally be a value that points into an allocated address range in your process. If that were to happen, you would not get an AV at the point of assignment, but would likely get a much worse and far more mysterious crash later in program execution because this string assignment to an uninitialized variable has altered memory somewhere else in your process inappropriately.

Whenever you're dealing with memory pointers directly and the type you're allocating contains compiler managed fields, you need to be very careful to initialize and dispose the type so that the compiler managed fields get initialized and disposed of correctly.

The best way to allocate records in Delphi is to use the New() function:

New(newItem);

The compiler will infer the allocation size from the type of the pointer (sizeof what the pointer type points to), allocate the memory, and initialize all the fields appropriately for you.

The corresponding deallocator is the Dispose() function:

Dispose(newItem);

This will make sure that all the compiler-managed fields of the record are disposed of correctly in addition to freeing the memory used by the record itself.

If you just FreeMem(newItem), you will leak memory because the memory occupied by the strings and other compiler managed fields in that record will not be released.

Compiler managed types include long strings ("String", not "string[10]"), wide strings, variants, interfaces, and probably something I've forgotten.

One way to think about is this: GetMem/FreeMem simply allocate and release blocks of memory. They know nothing about how that block will be used. New and Dispose, though, are "aware" of the type you're allocating or freeing memory for, so they will do any additional work to make sure that all the internal housekeeping is taken care of automatically.

In general, it's best to avoid GetMem/FreeMem unless all you really need is a raw block of memory with no type semantics associated with it.

like image 183
dthorpe Avatar answered Oct 19 '22 22:10

dthorpe


Also records are value types (as opposed to reference types like tObject) so you can just do

AccessoryItem1 := AccessoryItem2;

(or AccessoryItem1^ := AccessoryItem2^ if they are pointers)

and it will copy all the fields over for you.

For the same reason you should be careful when passing them around as method parameters - unless you use a pointer delphi will create a new copy of the record everytime you call a method with it as a parameter.

like image 20
Frank Wallis Avatar answered Oct 20 '22 00:10

Frank Wallis