You feed a program six scores and it reports five. The file scores.txt holds 72, 85, 91, 64, 78 and 67, one per line. The program prints a count of 5 and a mean of 78.0, with no crash and no warning. The true mean is 76.2, which is 457 divided by 6.
program average;
{ Version 1: a fixed array of five, and a sixth score that never lands. }
const
MAX = 5;
var
scores: array[1..MAX] of integer;
n, i, sum: integer;
begin
n := 0;
sum := 0;
for i := 1 to MAX do
if not eof then
begin
readln(scores[i]);
n := n + 1;
sum := sum + scores[i];
end;
writeln('count: ', n);
writeln('mean: ', sum / n:0:1);
end.
$ fpc average.pas && ./average < scores.txt
count: 5
mean: 78.0
The constant MAX stopped the for loop after five reads, and the sixth line stayed unread in the file. A static array has its size fixed when you write the program, and the loop obeys that size without complaint. Nothing in the file or the compiler flags the missing line, so the only way to catch it is to compare the output with the answer you expect.
Key takeaways
- A fixed
array[1..MAX]stops reading atMAXwithout a warning, which is how six scores produced the mean of five. - A dynamic array starts with
Length0 and equal tonil;SetLengthgives it a length, and the new elements arrive as zero, not as garbage. - Its indexes run from 0 to
High, where a staticarray[1..5]runs from 1 to 5. b := acopies the pointer, so writing throughbchangesa;Copy(a, start, count)is what makes independent storage.SetLength(a, 0)releases whatapoints at, but a second variable holding the same block keeps the data alive until the last reference goes.- Nothing stops a write past the end by default: build with
-Crto turn it intoRuntime error 201instead of silent corruption.
Give the array a length at run time
A dynamic array is declared as array of integer, with no bounds. The variable starts empty: its Length is 0 and it equals nil, the pointer value that refers to nothing. SetLength gives it a size.
program explore;
var
a, b: array of integer;
i: integer;
begin
writeln('before SetLength: Length = ', Length(a), ', a = nil is ', a = nil);
SetLength(a, 3);
writeln('after SetLength(a, 3): Length = ', Length(a), ', Low = ', Low(a), ', High = ', High(a));
write('elements:');
for i := Low(a) to High(a) do
write(' ', a[i]);
writeln;
writeln('sizeof(a) = ', sizeof(a));
a[0] := 10;
a[1] := 20;
a[2] := 30;
SetLength(a, 5);
writeln('grown to 5: Length = ', Length(a), ', kept ', a[0], ' ', a[1], ' ', a[2], ', new ', a[3], ' ', a[4]);
b := a;
b[0] := 99;
writeln('b[0] = 99, a[0] = ', a[0], ', same storage = ', @b[0] = @a[0]);
SetLength(a, 0);
writeln('after SetLength(a, 0): Length = ', Length(a), ', a = nil is ', a = nil);
writeln('b is still ', Length(b), ' elements, b[0] = ', b[0]);
end.
$ fpc explore.pas && ./explore
before SetLength: Length = 0, a = nil is TRUE
after SetLength(a, 3): Length = 3, Low = 0, High = 2
elements: 0 0 0
sizeof(a) = 8
grown to 5: Length = 5, kept 10 20 30, new 0 0
b[0] = 99, a[0] = 99, same storage = TRUE
after SetLength(a, 0): Length = 0, a = nil is TRUE
b is still 5 elements, b[0] = 99
The indexes run from 0 to 2, where array[1..5] runs from 1 to 5. New elements are zero, not leftover garbage. Growing from 3 to 5 keeps 10 20 30 and fills the two new slots with zeros.
sizeof(a) is 8 whatever the length, because the variable holds a pointer to the data and not the data. A static array[1..5] of integer occupies 10 bytes here: five integers of two bytes each, Free Pascal's default size. The dynamic version replaces those 10 bytes with an 8-byte pointer plus a heap block, which is memory the program requests while it runs.
Two names, one array
The assignment b := a copies the pointer, not the elements. After b[0] := 99, a[0] reads 99 too, and @b[0] = @a[0] is TRUE: both names reach the same storage. SetLength(a, 0) empties a, yet b still holds its five elements. Pascal counts references to the block and frees it when the last one goes.
Copy gives you independent storage. Copy(a, 1, 3) on an array holding 3 4 5 6 7 8 returns 4 5 6, because the start index is 0-based like the array itself. Copy(a, 4, 3) returns only 7 8, because only two elements were left. Changing the copy's first element to 999 left the original at 3, and @original[0] = @copy[0] was FALSE.
The exception: nothing guards the end
program probe4;
{ Out-of-bounds with an index the compiler cannot see. }
var
dyn: array of integer;
i: integer;
begin
SetLength(dyn, 3);
i := Length(dyn);
writeln('writing at index ', i, ' of length ', Length(dyn));
dyn[i] := 42;
writeln('survived, dyn[i] = ', dyn[i]);
end.
$ fpc probe4.pas && ./probe4
writing at index 3 of length 3
survived, dyn[i] = 42
Index 3 on a length-3 array writes into memory the array does not own, and the program exits with status 0. Range checking is off by default. Build with -Cr and the same write stops the program:
$ fpc -Cr probe4.pas && ./probe4
writing at index 3 of length 3
Runtime error 201 at $00000000004011D9
$00000000004011D9
$000000000042308C
The exit status is 201. A constant index is caught earlier: with -Cr, writing index 4 into a fixed array[1..3] stops compilation with Error: range check error while evaluating constants (4 must be between 1 and 3).
Make the check fail first
Save this as check_average.pas. It reads a program's output on stdin and compares it to two expected lines. For each line it prints PASS or FAIL, and a line that never arrives counts as a failure with the note "no such line".
program check_average;
{ Reads the program's output on stdin and compares it to the expected lines. }
var
expected: array[1..2] of string;
line: string;
i, passed, failed, total: integer;
procedure Report(ok: boolean; n: integer; want, got: string);
begin
if ok then
begin
writeln('PASS line ', n, ': ', want);
passed := passed + 1;
end
else
begin
if got = '' then
writeln('FAIL line ', n, ': expected "', want, '", no such line')
else
writeln('FAIL line ', n, ': expected "', want, '", got "', got, '"');
failed := failed + 1;
end;
end;
begin
expected[1] := 'count: 6';
expected[2] := 'mean: 76.2';
passed := 0;
failed := 0;
total := 2;
i := 0;
while (not eof) and (i < total) do
begin
readln(line);
i := i + 1;
Report(line = expected[i], i, expected[i], line);
end;
while i < total do
begin
i := i + 1;
Report(false, i, expected[i], '');
end;
writeln;
writeln(passed, ' of ', total, ' checks passed');
if failed > 0 then
ExitCode := 1;
end.
Compile the checker and version 1 of average.pas with fpc, then pipe one into the other.
$ ./average < scores.txt | ./check_average
FAIL line 1: expected "count: 6", got "count: 5"
FAIL line 2: expected "mean: 76.2", got "mean: 78.0"
0 of 2 checks passed
The checker exits with status 1 here, so a script can gate on it.
Switch to a dynamic array
Replace average.pas with version 2. The fixed array becomes array of integer, and SetLength(scores, n + 1) runs before each read.
program average;
{ Version 2: the array grows to fit whatever arrives. }
var
scores: array of integer;
n, sum: integer;
begin
n := 0;
sum := 0;
while not eof do
begin
SetLength(scores, n + 1);
readln(scores[n]);
sum := sum + scores[n];
n := n + 1;
end;
writeln('count: ', Length(scores));
writeln('mean: ', sum / n:0:1);
end.
Compile it and run the same pipe.
$ ./average < scores.txt | ./check_average
PASS line 1: count: 6
PASS line 2: mean: 76.2
2 of 2 checks passed
The checker now exits with status 0. Version 2 reports Length(scores) as its count, so the number comes from the array itself. The loop has no MAX to stop it, and the array ends up as long as the file.
Choosing between them
Ask who decides the count. If you decide it when you write the program, use array[1..N] with a constant. If the data decides it, use array of and index from 0. Build with -Cr while you test, because a dynamic array does not stop a write past its end by default.