0

I have created a dynamic array of records which is expanded using this actionexecute method:

procedure TForm1.AddTeamActionExecute(Sender: TObject);
Var
  c : integer;
begin
  c := length(PrjRecArray);
  PrjRecArray[c].tmpLoadPrjRec (true, 'Team', 'Big Building', '123 Main Street' ,'',
    'Somewhere', 'Ohio','43210', '555-1234', 'Bob', 'Big Cheese', '555-0123', 'bob@gmail.com');

  PrjSg.Cells[0,PrjSg.RowCount-1] := (PrjRecArray[c].Team);
  PrjSg.Cells[1,PrjSg.rowcount-1] := (PrjRecArray[c].Name);
  PrjSg.Cells[2,PrjSg.rowcount-1] := (PrjRecArray[c].addr1);
  PrjSg.Cells[3,PrjSg.rowcount-1] := (PrjRecArray[c].addr2);
  PrjSg.Cells[4,PrjSg.rowcount-1] := (PrjRecArray[c].city);
  PrjSg.Cells[5,PrjSg.rowcount-1] := (PrjRecArray[c].state);
  PrjSg.Cells[6,PrjSg.rowcount-1] := (PrjRecArray[c].zip);
  PrjSg.Cells[7,PrjSg.rowcount-1] := (PrjRecArray[c].phone);
  PrjSg.Cells[8,PrjSg.rowcount-1] := (PrjRecArray[c].contact);
  PrjSg.Cells[9,PrjSg.rowcount-1] := (PrjRecArray[c].title);
  PrjSg.Cells[10,PrjSg.rowcount-1] := (PrjRecArray[c].conPhone);
  PrjSg.Cells[11,PrjSg.rowcount-1] := (PrjRecArray[c].email);
  PrjSg.RowCount := PrjSg.RowCount + 1;
  Revised(true);
  showmessage ('PrSG Rows = ' + inttostr (PrjSg.RowCount));
  c := c + 1;
  SetLength (PrjRecArray, c);
  showmessage ('PrjRecArray Rows = ' + inttostr (length(PrjRecArray)));

end;

The array is called PrjRecArray declared in the unit ( PrjRecArray : Array of TPrjRec;) and is not otherwise initialized. PrjSg is a tstringgrid contained in the form and is used to display the records.

As I add more records using the AddTeamActionExecute the stringgrid continues to increase in size correctly. However, while the PrjRecordArray expands to four records correctly, the program apparently fails on the fifth iteration at the set length line. Execution hangs and never displays the second showmessage box.

Am I missing some step for using dynamic arrays properly?

Ashlar
  • 636
  • 1
  • 10
  • 25

2 Answers2

5

You access off the end of the array. Instead of

c := length(PrjRecArray);

write

c := length(PrjRecArray) - 1;

or

c := high(PrjRecArray);

Remember that dynamic arrays are zero based.

If you enabled range checking in the compiler options then you will encounter a runtime time error for any out of bounds array access which greatly helps debugging.

The call to SetLength would also need to be corrected. For instance

SetLength (PrjRecArray, length(PrjRecArray) + 1);

Rather than a dynamic array, using TList<T> might lead to simpler code to read and write. You can let TList<T> take care of the details of resizing its internal dynamic array.

My only other comment is that I wonder where the array is actually populated. You extend the length, but I don't obviously see where you assign any values.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
4

Length gives the current length of the array. But dynamic arrays start at index 0, and they end at -- in this case -- High(PrjRecArray). You should be accessing PrjRecArray[c - 1], not PrjRecArray[c].

Alternatively, use

c := High(PrjRecArray);

Then you can use

PrjSg.Cells[0, PrjSg.RowCount - 1] := PrjRecArray[c].Team;
// etc...

In your code, if you already have the length in c, and then set the length to that same c again, you are indeed not expanding, you are setting it to the same length it already had. Use

SetLength(PrjRecArray, Length(PrjRecArray) + 1);

Or, in your code (assuming c is the length):

SetLength(PrjRecArray, c + 1); // previous length + 1

FWIW, if you do that often, you might want to consider incrermenting the size in larger chunks (e.g. SetLength(PrjRecArray, c + c shr 1); -- or SetLength(PrjRecArray, c + c div 2);, which is the same), and keeping track of the actually used elements of the array. That avoids fragmenting the heap, which could make you run out of memory very easily.

More about that in my blog article about expanding arrays.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94