[DUG] [computing] Sizeof record gives error
David Brennan
dugdavid at dbsolutions.co.nz
Tue Aug 30 12:51:33 NZST 2011
I think the sizeof call is irrelevant here (which I believe is what Jolyon is also saying).
The error occurs on this line:
LogData.LogArgs := CreateConstArray(Args);
LogArgs is a dynamic array as is the CreateConstArray result. Standard Delphi logic is that the memory for the old dynamic array (ie LogArgs) will be dereferenced and then freed if no longer used. If LogArgs points to random memory because it was never initialised then obviously the dereferencing and subsequent freeing are both extremely likely to blow up.
The compiler usually works pretty hard to make sure dynamic array variables are initialised to nil but the GetMem call was bypassing that.
Cheers,
David.
From: delphi-bounces at listserver.123.net.nz [mailto:delphi-bounces at listserver.123.net.nz] On Behalf Of Jolyon Smith
Sent: Tuesday, 30 August 2011 12:04 p.m.
To: rohit at cfl.co.nz; NZ Borland Developers Group - Delphi List
Subject: Re: [DUG] [computing] Sizeof record gives error
No, it is not. The compiler is perfectly correct.
A "dynamic array" is a reference type. It is a POINTER to a structure that describes a dynamically sized and reference counted array, just as a "String" is a pointer to a structure that describes (in even more detail) a dynamically sized, reference counted String. In fact, an "array of Char", plus twiddly bits. :)
sizeof(TObject) = 4
sizeof(Pointer) = 4
sizeof(String) = 4
sizeof(dynamic array) = 4
Is correct and true because all four types are _pointers_ and (on Win32) a pointer is 32-bits == 4 bytes.
The size of/amount of memory that each variable of those types points _to_ is different, and includes not only the "user data" (at the positive offsets from the address that the pointer references) but also RTL information at *negative* offsets. But "sizeof" is not concerned with (is in fact entirely ignorant of) this data, only the size of the type used to hold the *reference* to that data.
type
tfoo = record
bar: array of integer;
end;
var
f: TFoo; // << has a single field holding a *reference* to an empty array of integer;
begin
// sizeof(f) == 4 at this point - a pointer
SetLength(f.bar, 20); // makes "bar" big enough for 20 integers - 80 bytes
// sizeof(f) is STILL *4* at this point because "bar" is still just a pointer.
// Furthermore, sizeof() is evaluated when you *compiled* the code so it cannot know
// how much memory bar points to even if it wanted to.
end;
It is the *initialization* of the memory *referenced* by your dynamic array variable that is crucial to the correct functioning of the "compiler magic" that supports such types. If you run this code in the debugger and place a breakpoint on the "SetLength", when the code stops on that breakpoint open the CPU view and look back and you will see that the compiler has injected a call to "InitializeRecord"... if you look in System.pas you will see that _InitializeRecord in turn calls _InitializeArray.
It is this sequence of "Initialize" calls that is missing when you simply allocate a chunk of memory and then set a typed pointer to point at it without first doing all the housekeeping necessary to make that chunk of memory *behave* in the way that your typed pointer is going to expect it to.
By all means do that simple memory allocation, but you are then responsible for doing the housekeeping... calling "Initialize".
Or use "New" and let the RTL take care of any initialisation that may be required.
On 30 August 2011 09:10, Rohit Gupta <rohit at cfl.co.nz> wrote:
Joylon,
Good point. But I dont think it applies here. Sizeof returns an incorrect result... Its a compiler flaw.
Rohit
On 29/08/2011 8:10 p.m., Jolyon Smith wrote:
Having just looked up the references for New/GetMem it may not actually be a question of amount of memory allocated after all, but rather one of correct initialization of the allocated memory.
GetMem() does not initialize the allocated memory, New() does.
This is explained in the documentation for the "Initialize()" procedure (it might have been helpful if the documentation for GetMem() had mentioned this - or the lack there-of - too - LOL)
So you might find that GetMem() would have worked had you also called Initialise() on the pointer afterward (and Finalize() before calling FreeMem()). But whatever the underlying reasons it is obviously simply much easier and safer (less to remember to have to do, less to get wrong in doing it :)) to use New/Dispose when working with allocations of variables/structured(typed) memory and use GetMem/FreeMem when it is strictly speaking *just* unstructured memory you need (e.g. i/o buffers etc), rather than space for variables.
Glad it helped with your problem in any event. :)
On 29 August 2011 17:40, David Moorhouse <delphi at moorhouse.net.nz> wrote:
I'm aware of the compiler layout behind the scene - and the fact that regardless of the length of the dynamic array, my call to GetMem (or New) does NOT have to allocate memory for the dynamic array's contents, just it's overhead :)
However, the compiler gets the size right using New rather than GetMem :) So thanks for the tip.
Cheers
D
On 29/08/11 16:23, Jolyon Smith wrote:
Is it a compiler *error* or just a compiler _behaviour_ ?
I haven't looked into it in detail, but dynamic arrays are notoriously slippery when you are working with them at a low level and alarm bells started ringing as soon as I saw they were involved.
In particular, a dynamic array is a reference type, like a string. So whilst their may be additional RTTI at a negative offset from the base address of the array, the "array" itself may well be a pointer, hence "sizeof()" will return 4 - the size of a pointer - no matter how many items may be in the array (as opposed to Length(), obviously).
NOTE: sizeof(String) also yields "4" even though we all know that a String variable requires many more bytes than that.
As far as this particular example goes, do you get any better results using the typed New() function rather than GetMem() which knows nothing about the "type" of memory required by the pointer you are initialising and just blithely allocates the specified number of bytes...:
Instead of >> LogData := GetMem( ... );
Use >> New( LogData );
And see if you get better results. :)
(Also, don't forget to use "Dispose()" to deallocate the memory obtained with "New()", rather than FreeMem())
On 28 August 2011 21:33, David Moorhouse (DUG) <delphi at moorhouse.net.nz> wrote:
I believe it is a compiler error and will raise a QA ticket
Thanks for your help
D
On 26/08/11 08:17, Peter Ingham wrote:
> Try filling LogData with binary zeros after the Getmem& before the assign.
>
> FillChar (LogData^, sizeof(TLogData), 0);
>
> I believe the uninitialized memory is messing up the compiler magic
> associated with the dynamic array.
>
>
> Any reason the local Tlogdata record is referenced via a pointer?
>
> I suspect the following will also work:
> procedure TUserClass.Log(const LogType: TLogType; const Args: array of
> const );
> var
> LogData: TLogData;
> begin
> LogData.LogType := LogType;
> LogData.LogArgs := CreateConstArray(Args);
> // ... do some other stuff with the LogData item finally calling
> end;
>
> Cheers
>
> On 26/08/2011 1:49 a.m., David Moorhouse wrote:
>> Hi Peter
>>
>> Been there done that :)
>>
>> The function call is fine. It is the assignment that causes the AV -
>> because the "bucket" is too small.
>> Assigning it with 16 bytes fixes the problem, regardless of how many
>> items the array holds.
>>
>> I smell compiler magic in the background.
>>
>> Cheers
>
>> D
>>
>> On 25/08/11 17:29, Peter Ingham wrote:
>>> Another attempt to reply...
>>>
>>> First thing to do is determine if the crash occurs in the procedure call,
>>> on the subsequent assign, or in between.
>>>
>>> Give this a try:
>>> procedure TUserClass.Log(const LogType: TLogType; const Args: array of
>>> const );
>>> var
>>> LogData: PLogData;
>>> TempArgs : TConstArray;
>>> begin
>>> // size of record TLogData does not work
>>> GetMem(LogData, sizeof(TLogData));
>>> LogData.LogType := LogType;
>>> // blows up on one of these lines
>>> TempArgs := CreateConstArray(Args);
>>> LogData.LogArgs := TempArgs;
>>> // ... do some other stuff with the LogData item finally calling
>>> FreeMem
>>> end;
>>>
>>>
>>> Regarding the size of a dynamic array, like a string variable, the
>>> variable (LogArgs in this case) is the size of a pointer (i.e. 4 bytes
>>> for Win32). If the pointer is non-zero, it points to a structure which
>>> includes the adjacent array elements preceded by a length.
>>>
>>> One thing to watch out for is that Getmem does not clear the allocated
>>> memory, so LogData after the Getmem call will contain any old rubbish.
>>> The reference to LogData.LogArgs in the assignment may be
>>> dereferencing a non-zero pointer& attempting to use whatever it
>>> contains.
>>>
>>> Cheers
>>>
>>>
>>> On 25/08/2011 11:40 a.m., David Moorhouse (DUG) wrote:
>>>> I have the following code snippet
>>>>
>>>> <code>
>>>> type
>>>> PConstArray = ^TConstArray;
>>>> TConstArray = array of TVarRec;
>>>>
>>>> function CreateConstArray(const Elements: array of const): TConstArray;
>>>>
>>>> type
>>>> TLogType = (ltError, ltWarn, ltInfo);
>>>> PLogData = ^TLogData;
>>>> TLogData = record
>>>> LogType: TLogType;
>>>> LogArgs: TConstArray;
>>>> end;
>>>>
>>>> ....
>>>>
>>>> procedure TUserClass.Log(const LogType: TLogType; const Args: array of
>>>> const );
>>>> var
>>>> LogData: PLogData;
>>>> begin
>>>> // size of record TLogData does not work
>>>> GetMem(LogData, sizeof(TLogData));
>>>> LogData.LogType := LogType;
>>>> // blows up on next line
>>>> LogData.LogArgs := CreateConstArray(Args);
>>>> // ... do some other stuff with the LogData item finally calling
>>>> FreeMem
>>>> end;
>>>>
>>>> function CreateConstArray(const Elements: array of const): TConstArray;
>>>> var
>>>> I: Integer;
>>>> begin
>>>> SetLength(Result, Length(Elements));
>>>> for I := Low(Elements) to High(Elements) do
>>>> Result[I] := // assign a TVarRec here
>>>> end;
>>>> </code>
>>>>
>>>> The code that assigns the memory only assigns 8 bytes - and an access
>>>> violation ensues. If I replace the call to "sizeof" with the number 16,
>>>> the code works fine.
>>>>
>>>> My understanding of dynamic arrays was that the compiler created a 4
>>>> byte
>>>> field before the first element that contained the length of the array.
>>>>
>>>> So why does the sizeof function not reflect this ? And why do I
>>>> need 16
>>>> bytes not 12 (4 for LogType + 4 for length of array + 4 for array
>>>> pointer)?
>>>> Also regardless of the number of items in the open array parameter, 16
>>>> bytes works, so it does not relate the length of the TConstArray.
>>>>
>>>> Your thoughts ?
>>>>
>>>> David
>>>>
>>>>
_______________________________________________
NZ Borland Developers Group - Delphi mailing list
Post: delphi at listserver.123.net.nz
Admin: http://delphi.org.nz/mailman/listinfo/delphi
Unsubscribe: send an email to delphi-request at listserver.123.net.nz with Subject: unsubscribe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://listserver.123.net.nz/pipermail/delphi/attachments/20110830/6583e6a7/attachment-0001.html
More information about the Delphi
mailing list