Quote
James R. Darrah wrote:
> I am trying to create a series of arrays dynamically using getmem. I
> have one array that is statically defined that will point to one of 10
> other "arrays" that are dynamically allocated. I need to do this so
> that if I need 8 bit data I only allocate arrays for byte data or if I
> only need 16 bit data I will allocate arrays for word data. The need
> will vary depending on several other factors. I don't want to over
> allocate data just to be prepared to accept either 8 or 16 bit data. I
> tried to allocate an array which is approximately 22000 elements using a
> getmem and saving a pointer from the static pointer array to the newly
> acquired storage. This seemed to work OK, however when I tried to use
> the storage I got an access violation writing data to one of the
> arrays. From a dump of the pointers in my static pointer array I can
> tell that I have indeed allocated blocks of storage for data at the
> failing address of 93C000 in hex. My suspicion is that when I used INC
> procedure to increment the pointer it got lost when I crossed a page
> boundry, giving the exception. What is the correct way to dynamically
> allocate an array? How do I put data into the new array? Thanks for any
> help you can give.
I surmise that you are working in a 16-bit environment. Many of the
potential problems have been solved for you by other container-classes,
such as TMemoryStream.
But, having said that, let me loosen-up a bit and say, take a closer
look at container classes of all kinds. You can download 'em, and you
can buy 'em. Who says that the only way to store 22,000 words in a
fashion that "looks" like an array, is actually to allocate contiguous
storage and wrestle with it?
In one of my projects I had to deal with a situation where I could have
up to 65,536 words but usually far less. So I constructed a
container-class which stores the elements as a one-level tree with 32
leaves, each containing 2,048 words. Leaves are allocated only when a
non-zero value needs to be stored. The leaves don't have to be
physically contiguous in memory. The "read" and "write" methods of this
container-class are only a few instructions long, but it completely
eliminates this very-similar problem.
This class is reproduced below:
{? Copyright Sundial Services. Permission to use granted.}
interface
type
TWordArray = class(TObject)
FMinSize,
FMaxSize: Word;
FPages: array[0..31] of Pointer;
procedure CheckElement(element: Word);
function GetElement(element: Word): Word;
procedure PutElement(element, value: Word);
public
constructor Create(minSize, maxSize: Word);
destructor Destroy;
property Elements[element: Word]: Word
read GetElement write PutElement; default;
property MinSize: Word
read FMinSize write FMinSize;
property MaxSize: Word
read FMaxSize write FMaxSize;
procedure Clear;
procedure Exclude (element, value: Word);
procedure Include (element, value: Word);
function Includes(element, value: Word): boolean;
end; {TWordArray}
implementation
{
TWORDARRAY: AN ARRAY OF 16-BIT VALUES.
type
TWordArrayPage = array[0..2047] of Word;
PWordArrayPage = ^TWordArrayPage;
constructor TWordArray.Create(minSize, maxSize: Word);
begin
inherited Create;
FMinSize := minSize;
if maxSize = 0
then FMaxSize := 65535 { to simplify loops that want
to iterate from min to max ... }
else FMaxSize := maxSize;
if (FMaxSize < FMinSize)
then raise EInvalidOperation.CreateFmt( 'impossible bounds [%d,
%d]',
[ FMinSize, FMaxSize ] );
end;
destructor TWordArray.Destroy;
begin
Clear; { we rely upon this method to
physically delete the pages }
inherited Destroy;
end;
{
CheckElement:
It turns out that the most convenient way to handle errors in
referencing these arrays
or bit-sets, is to provide an exception handler here in these
types.
If the max-size is zero, it means that no test should occur.
procedure TWordArray.CheckElement(element: Word);
begin
if (FMaxSize > 0) and (FMaxSize < 65535) and (element > FMaxSize)
then raise EInvalidOperation.CreateFmt( 'access array-element %d,
but max size is %d',
[ element, FMaxSize ] );
if (FMinSize > 0) and (element < FMinSize)
then raise EInvalidOperation.CreateFmt( 'access array-element %d,
but min size is %d',
[ element, FMinSize ] );
end; {CheckElement}
{
The "GetElement" routine takes advantage of the fact that unused
elements
are always defined to be zero.
function TWordArray.GetElement(element: Word): Word;
var
i, m: Word;
begin
CheckElement(element);
i := (element shr 11) and $001F; { the "and" is
unnecessary, but hey ... }
m := element and $07FF;
if FPages[i] = nil
then result := 0
else result := PWordArrayPage(FPages[i])^[m];
end; {GetElement}
{
The "PutElement" routine allocates pages as required, always setting
the entire
page to binary zeroes before storing the value.
procedure TWordArray.PutElement(element, value: Word);
var
i, m: Word;
begin
CheckElement(element);
i := (element shr 11) and $001F; { the "and" is
unnecessary, but hey ... }
m := element and $07FF;
if FPages[i] = nil then begin { no page here
yet? }
if value = 0 then exit; { well, no need to
burn a page just to store a zero! }
FPages[i] := AllocMem(sizeof(TWordArrayPage)); { otherwise,
allocate page, set it to zero }
end;
PWordArrayPage(FPages[i])^[m] := value;
end; {PutElement}
{
"Include" / "Exclude" / "Includes" -- faster logical-bit routines.
These operations are not implemented for all the array-types, simply
because
we don't need them!
procedure TWordArray.Include(element, value: Word);
var
i, m: Word;
begin
CheckElement(element);
if value = 0 then exit; { don't waste my
time }
i := (element shr 11) and $001F; { the "and" is
unnecessary, but hey ... }
m := element and $07FF;
if FPages[i] = nil then begin { no page here yet?
FPages[i] := AllocMem(sizeof(TWordArrayPage)); { gotta have one,
so allocate page, set it to zero }
end;
PWordArrayPage(FPages[i])^[m] := PWordArrayPage(FPages[i])^[m] or
value;
end; {Include}
procedure TWordArray.Exclude(element, value: Word);
var
i, m: Word;
begin
CheckElement(element);
if value = 0 then exit; { don't waste my
time }
i := (element shr 11) and $001F; { the "and" is
unnecessary, but hey ... }
m := element and $07FF;
if FPages[i] = nil then exit; { if no page here
yet, there's nothing to exclude }
PWordArrayPage(FPages[i])^[m] := PWordArrayPage(FPages[i])^[m] and
(not value);
end; {Exclude}
function TWordArray.Includes(element, value: Word): boolean;
var
i, m: Word;
begin
result := false; { default result }
CheckElement(element);
if value = 0 then exit; { if "includes 0"
... result always false }
i := (element shr 11) and $001F; { the "and" is
unnecessary, but hey ... }
m := element and $07FF;
if FPages[i] = nil then exit; { page isn't here
... result is false }
if (PWordArrayPage(FPages[i])^[m] and value) = value then result :=
true; { all bits must be true }
end; {Includes}
{
Clearing an array consists of disposing of all of the existing
data-storage pages
and setting the corresponding pointers to NIL.
(N.B. "Destroy" relies on this, and "GetElement" takes advantage of
it.)
procedure TWordArray.Clear;
var
w: 0..31;
begin
for w := 0 to 31 do
if FPages[w] <> nil then begin
FreeMem(FPages[w], sizeof(TWordArrayPage));
FPages[w] := nil;
end;
end;