Hi, Let's suppose the following code, which is used as sample, that is to declare a Constant array of record: type TShopItem = record Name: string; Price: currency; end; const Items: array1.3 of TShopItem = ( (Name: 'Clock'; Price: 20.99), (Name: 'Pencil'; Price: 15.75), (Name: 'Board'; Price: 42.96) ); What if I want to declare a to declare a Constant array of array of record (array of array). How do I declare that? Taking the previous classes, now suppose that I have 'groups' of TShopItem's, like: Group 1 (Name: 'Clock'; Price: 20.99) (Name: 'Pencil'; Price: 15.75) Group 2 (Name: 'Board'; Price: 42.96) (Name: 'Eraser'; Price: 2.96) Group 3 (note different length) (Name: 'Paper'; Price: 5.96) (Name: 'Pen'; Price: 2.96) (Name: 'Postits'; Price: 2.96) How do I put all that in a single constant array of array declaration? You cannot because the groups 1, 2 & 3 do not have the same size, so you cannot declare a single array.
Or you declare an array of 3x3 and there will be some holes (unused elements) const Items: array1.3, 1.3 of TShopItem = ( ( (Name: 'Clock'; Price: 20.99), (Name: 'Pencil'; Price: 15.75), (Name: 'Board'; Price: 42.96) ), ( (Name: 'Paper'; Price: 5.96), (Name: 'Pen'; Price: 2.96), (Name: 'Postits'; Price: 2.96) ), ( (Name: 'Clock'; Price: 20.99), (Name: '-'; Price: 0), (Name: '-'; Price: 0) ) ); Select all. A constant for a price? Isn't that a completely wrong approach? I have never known a price to be constant (only for a day. And then still.) i would come for Petrol at your place.
Especially if your shop would have existed 50 years ago. About the idea. Why not use a TCollection to hold the items and a TCollectionItem to contain a specific item type TShopItem = class(TCollection) private fPrice: Double; fName: string; fGroup: string; // single group or multiple group??? You consider create a TCollection Group too:) public property Name: string read fName write SetName; property Price: Double read fPrice write SetPrice; property Group: string read fGroup write SetGroup; end; TShopItems = class(TCollection) end; at the start of your program: procedure InitTestData; procedure AddItem(aName: string; aPrice: double; aGroup: string); begin with ShopItems.Add do begin Name:= aName; Price:= aPrice; Group:= aGroup; end; end; begin AddItem('Clock', 20.99, 'Group 1'); AddItem('Pencil', 15.75, 'Group 1'); AddItem('Board', 42,96, 'Group 2'); end. My only concern is how to make all those available at design time for my property editors I don't see why you couldn't create your own property editor based on this principle to make it easy for the programmer to fill that structure at design time.
Just have to find a persistent way of storing all that information (like exporting in a string). I'm not that used to make property editors, but if I remember, Geert have more experience about it and would be glad to learn also:o) Or he is not listening, and I will have to dig tomorrow for a solution.
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 is my note on this topic. IMO, declare a constant with default value is a better way.
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 Fill Char, it is really Fill Byte. 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. If you have Delphi 2009 and later, use the Default call to initialize a record. Foo:= Default(TFoo); See to the question. 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. 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? The question may also be asking:. why FillChar. and not?
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 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.