Why most Delphi examples use FillChar() to initialize records?
Asked Answered
A

8

31

I just wondered, why most Delphi examples use FillChar() to initialize records.

type
  TFoo = record
    i: Integer;
    s: string; // not safe in record, better use PChar instead
  end;

const
  EmptyFoo: TFoo = (i: 0; s: '');

procedure Test;
var
  Foo: TFoo;
  s2: string;
begin
  Foo := EmptyFoo; // initialize a record

  // Danger code starts
  FillChar(Foo, SizeOf(Foo), #0);
  s2 := Copy("Leak Test", 1, MaxInt); // The refcount of the string buffer = 1
  Foo.s = s2; // The refcount of s2 = 2
  FillChar(Foo, SizeOf(Foo), #0); // The refcount is expected to be 1, but it is still 2
end;
// After exiting the procedure, the string buffer still has 1 reference. This string buffer is regarded as a memory leak.

Here (http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html) is my note on this topic. IMO, declare a constant with default value is a better way.

Adamok answered 23/4, 2009 at 22:36 Comment(0)
C
39

Historical reasons, mostly. FillChar() dates back to the Turbo Pascal days and was used for such purposes. The name is really a bit of a misnomer because while it says FillChar(), it is really FillByte(). The reason is that the last parameter can take a char or a byte. So FillChar(Foo, SizeOf(Foo), #0) and FillChar(Foo, SizeOf(Foo), 0) are equivalent. Another source of confusion is that as of Delphi 2009, FillChar still only fills bytes even though Char is equivalent to WideChar. While looking at the most common uses for FillChar in order to determine whether most folks use FillChar to actually fill memory with character data or just use it to initialize memory with some given byte value, we found that it was the latter case that dominated its use rather than the former. With that we decided to keep FillChar byte-centric.

It is true that clearing a record with FillChar that contains a field declared using one of the "managed" types (strings, Variant, Interface, dynamic arrays) can be unsafe if not used in the proper context. In the example you gave, however, it is actually safe to call FillChar on the locally declared record variable as long as it is the first thing you ever do to the record within that scope. The reason is that the compiler has generated code to initialize the string field in the record. This will have already set the string field to 0 (nil). Calling FillChar(Foo, SizeOf(Foo), 0) will just overwrite the whole record with 0 bytes, including the string field which is already 0. Using FillChar on the record variable after a value was assigned to the string field, is not recommended. Using your initialized constant technique is a very good solution this problem because the compiler can generate the proper code to ensure the existing record values are properly finalized during the assignment.

Corpus answered 24/4, 2009 at 3:50 Comment(3)
>>> Using FillChar on the record variable after a value was assigned to the string field, is not recommended In such cases you should use Finalize(V); FillChar(V, SizeOf(V), 0); If you do this frequently, you may write a new function, which does Finalize + FillChar. I think that using FillChar will be faster then assigning to a constant, but it is probably not relevant today. More importantly - you do not need to declare a constant for every record's type.Erminois
>> I think that using FillChar will be faster then assigning to a constant, but it is probably not relevant today. More importantly - you do not need to declare a constant for every record's type. << This is why I regard this as an abuse. It would be great, if compiler supports reserve word "void", so that we can just write "Foo := void;" to initialize a record.Adamok
i use it whenever i can since it helps me follow Open/Closed principle. then when a record has a new (non-string) member added, further initialization is not needed.Useful
V
22

If you have Delphi 2009 and later, use the Default call to initialize a record.

Foo := Default(TFoo); 

See David's answer to the question How to properly free records that contain various types in Delphi at once?.

Edit:

The advantage of using the Default(TSomeType) call, is that the record is finalized before it is cleared. No memory leaks and no explicit dangerous low level call to FillChar or ZeroMem. When the records are complex, perhaps containing nested records etc, the risk of making mistakes is eliminated.

Your method to initialize the records can be made even simpler:

const EmptyFoo : TFoo = ();
...
Foo := EmptyFoo; // Initialize Foo

Sometimes you want a parameter to have a non-default value, then do like this:

const PresetFoo : TFoo = (s : 'Non-Default'); // Only s has a non-default value

This will save some typing and the focus is set on the important stuff.

Vikki answered 16/6, 2012 at 20:12 Comment(3)
Thanks for your information. IMO, for old delphi compilers we should define some default record constants rather than use the magic FillChar function. For modern Delphi compilers, we should declare constructors for record types as well. If you have interests, you can checkout the open source projects from my profile page. I code these projects according to the principle I pointed in the comment.Adamok
Added some simplifications to define const records.Vikki
Thanks for wonderful () construction, I've had no idea Delphi can do it.Vortex
O
9

FillChar is fine to make sure you don't get any garbage in a new, uninitialized structure (record, buffer, arrray...).
It should not be used to "reset" the values without knowing what your are resetting.
No more than just writing MyObject := nil and expecting to avoid a memory leak.
In particulart all managed types are to be watched carefully.
See the Finalize function.

When you have the power to fiddle directly with the memory, there is always a way to shoot yourself in the foot.

Openminded answered 24/4, 2009 at 0:52 Comment(2)
it happend to me, before, sometimes i didn't use fillchar for my record, and some should_be_empty members of that record got garbage string, since then i always use fillchar or zeromemory.Afebrile
To Francois: I did not ask for confirming this issue. I understand that there is no problem if it is used /carefully/. I just wondered, why FillChar has been used for so many years for intializing records.Adamok
O
4

FillChar is usually used to fill Arrays or records with only numeric types and array. You are correct that it shouldn't be used to when there are strings (or any ref-counted variables) in the record.

Although your suggestion of using a const to initialize it would work, an issue comes into play when I have a variable length array that I want to initialize.

Ovule answered 23/4, 2009 at 23:12 Comment(0)
E
4

The question may also be asking:

There is no ZeroMemory function in Windows. In the header files (winbase.h) it is a macro that, in the C world, turns around and calls memset:

memset(Destination, 0, Length);

ZeroMemory is the language neutral term for "your platform's function that can be used to zero memory"

The Delphi equivalent of memset is FillChar.

Since Delphi doesn't have macros (and before the days of inlining), calling ZeroMemory meant you had to suffer the penalty of an extra function call before you actually got to FillChar.

So in many ways, calling FillChar is a performance micro-optimization - which no longer exists now that ZeroMemory is inlined:

procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); inline;

Bonus Reading

Windows also contains the SecureZeroMemory function. It does the exact same thing as ZeroMemory. If it does the same thing as ZeroMemory, why does it exist?

Because some smart C/C++ compilers might recognize that setting memory to 0 before getting rid of the memory is a waste of time - and optimize away the call to ZeroMemory.

I don't think Delphi's compiler is as smart as many other compilers; so there's no need for a SecureFillChar.

Estonian answered 20/8, 2018 at 17:43 Comment(0)
A
2

Traditionally, a character is a single byte (no longer true for Delphi 2009), so using fillchar with a #0 would initalize the memory allocated so that it only contained nulls, or byte 0, or bin 00000000.

You should instead use the ZeroMemory function for compatibility, which has the same calling parameters as the old fillchar.

Abuttal answered 23/4, 2009 at 23:12 Comment(4)
Why do you keep answering the same questions as me at the same time? This time you beat me by 35 seconds.Ovule
must be something in the water.Abuttal
FillChar works the same as it always has. Its name isn't quite so appropriate anymore, but other than that, there's no difference.Grantinaid
ZeroMemory() does not have the same parameters. First, FillChar() expects 3 args, while ZeroMemory() expects only 2 args. Next, FillChar() needs a variable reference, ZeroMemory() wants a pointer (at least with XE and in the raw API). I have a different definition for "same".Cab
J
1

This question has a broader implication that has been in my mind for ages. I too, was brought up on using FillChar for records. This is nice because we often add new fields to the (data) record and of course FillChar( Rec, SizeOf( Rec), #0 ) takes care of such new fields. If we 'do it properly', we have to iterate through all fields of the record, some of which are enumerated types, some of which may be records themselves and the resulting code is less readable as well be possibly erroneous if we dont add new record fields to it diligently. String fields are common, thus FillChar is a no-no now. A few months ago, I went around and converted all my FillChars on records with string fields to iterated clearing, but I was not happy with the solution and wonder if there is a neat way of doing the 'Fill' on simply types (ordinal / float) and 'Finalize' on variants and strings?

Jannjanna answered 24/4, 2009 at 9:29 Comment(3)
The function 'Finalize' does help. But I still suggest do it properly by assigning it to a const empty record. Once you changed the structure of your record, compiler will remind you to update this empty record as well.Adamok
Brian, see my answer and follow the link. This is the neat answer you are looking for :)Vikki
Finalize + ZeroMemory/FillChar will cover all cases and satisfy all needs. And it probably is faster than assigning to constant empty record.Vortex
A
0

Here is a better way to initialize stuff without using FillChar:

Record in record (Cannot initialize)
How to initialize a static array?

Anodic answered 12/7, 2011 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.