Reputation: 1
I have a few const arrays of the same base type but different sizes, and I need to point to them in the const records. The code below compiles successfully, but finishes with error.
type
Toffsets = array of integer;
Trec = record
point1: Tpoint; //complete size
point2: Tpoint;
aOffsets: ^Toffsets;
end;
const
cOffsetsA: array [0..3] of integer = (7, 4, 2, 9);
cOffsetsB: array [0..5] of integer = (1, 2, 3, 4, 5, 6);
cRec1: Trec = (
point1: (x: 140; y: 46);
point2: (x: 5; y: 7);
aOffsets: @cOffsetsA;
);
cRec2: Trec = (
point1: (x: 40; y: 6);
point2: (x: 5; y: 7);
aOffsets: @cOffsetsB;
);
In my code I need to access data from the cOffsetsA/B arrays having a pointer to the record. I tried to do it like this:
var pMyRec: ^Trec;
...
pMyRec := @cRec1;
...
i := pMyRec^.aOffsets^[0];
and this causes error - 'Access violation ... read of address 000000...'
Can anybody explain is wrong here and how to fix it, how should it be done?
Probably I will also have to add the _length field in the record, which will hold the size of the array the pointer is pointing to; this is not a problem.
Best regards, LUK
Upvotes: 0
Views: 4555
Reputation: 26682
While the use of constants like this are okay in Delphi, I just want to add that it's in general not a very good idea to do things this way. Personally, if this code is in some unit, I would use something like this: (D2007 and higher)
type
Toffsets = array of integer;
Trec = record
point1: Tpoint; //complete size
point2: Tpoint;
aOffsets: ^Toffsets;
constructor Create(X1, Y1, X2, Y2: Integer; Offsets: array of integer);
end;
Thus, the record gets a constructor which will fill it with the proper data. As a disadvantage, you can't declare it as a const anymore, if you want to use the constructor to fill it with data. However, you can solve this by using the following code in the Initialization section:
var
cRec1: Trec;
initialization
cRec1 := Trec.Create(140, 6, 5, 7, cOffsetsA);
This code will declare the record as a global variable instead of constant, and fill it with your data.
Your constructor could look like this:
constructor Trec.Create(X1, Y1, X2, Y2: Integer; Offsets: array of integer);
var
I: Integer;
begin
point1.X := X1;
point1.Y := Y1;
point2.X := X2;
point2.Y := Y2;
new(aOffsets);
SetLength(aOffsets^, 0);
for I in Offsets do
begin
SetLength(aOffsets^, Succ(Length(aOffsets^)));
aOffsets^[Pred(Length(aOffsets^))] := I;
end;
end;
Which will fill the record with your data. However, you don't have to use the constructor to create records this way! It's just an added function.
To make it even more interesting, you can add properties for the two points and array, making the fields themselves private and only declaring read method for the properties. This way, you can still make sure it's content is read-only and stays read-only.
But what's the risk of using your code? Well, since you declare them all as constants, there isn't much risk, unless you allow assignable typed constants. ({$J+} is declared...) In that case, a piece of code could change a value in cOffsetsA and it would also change in cRec1. Is that what you want?
Anyway, by using a constructor and by initializing them in code, you make your code more dynamic. However, it still continues to stay a simple record type and the additional properties and methods won't change it's structure or size.
You will need Delphi 2007 or better for this, though...
Upvotes: 1
Reputation: 1
It looks like I found the answer myself:
type
Toffsets = array of integer;
is a dynamic array type and setLength must be used with any variable of this type to allocate the memory. What I need is a pointer to an existing constant array, so the only thing which must be changed in order to fix the problem is the declaration of this type. It should look like:
type
Toffsets = array [0..0] of integer;
and yes, I have to add the _length field in the records as low(pMyRec^.aOffsets^), high(...) and length(...) do not work.
Best regards, LUK
Upvotes: 0