Segment Arrays (Advanced)

Top  Previous  Next

Segment arrays solve the problem of the limited 60K heap space. You can have arrays as large as one megabyte.

 

The 8088 microprocessor used in the original IBM PC can address one megabyte of memory. Unfortunately, this memory is divided into 64K-byte blocks called segments. Memory is addressed by a combination of two 16-bit values called a "segment" and an "offset". The value in a segment register is multiplied by 16 and added to the offset to give a 20-bit number that can address one megabyte. This method of accessing memory does not work well for high-level languages because each variable must be addressed using both a segment and an offset. This slows every memory access and complicates pointers.

 

Other languages deal with this problem by using several different memory models. Each model addresses memory differently. For example, the "tiny memory model" is used by programs that run in less than 64K. In this case addressing is simple: The segment register is set once, and only the offset part of the address is used. If a program needs more than 64K, the "large memory model" might be chosen, which uses both a segment and an offset.

 

The native versions of the XPL0 compilers (those that produce .EXE instead of .COM files) allow code up to one megabyte, and programs that use segment arrays can have up to one megabyte of data. Of course the actual memory space available is typically less than 640K, the size of conventional memory. If your code needs more memory, you can divide it into modules and use the Chain intrinsic to run a piece at a time. If your data needs more memory, you can use EMS (Expanded Memory Specification) BIOS interrupt $67 or XMS (eXtended Memory Specification) interrupt $15.

 

Segment arrays are like other arrays except that they can reside in any segment of memory. They can be integer, real, or character arrays. Segment arrays are declared using the form:

 

        segment TYPE NAME(DIMENSION), ... NAME(DIMENSION);

 

For example:

 

        seg int  Length, Angle, Depth;

        seg real Rain, Snow;

        seg char BitMask, OrMask, AndMask;

 

Segment arrays are always 2-dimensional and are normally used with two subscripts. For example:

 

        Depth(X, Y):= 1530;

 

 

The first dimension contains a list of 16-bit "segment addresses". The second dimension contains a 16-bit offset. When an element of a segment array is accessed, the segment address and offset are combined to form a 20-bit address. Since the microprocessor automatically combines segments and offsets, this operation is relatively fast.

 

ALLOCATING MEMORY (Advanced)

 

Segment arrays differ from normal arrays in the way they are reserved. Since segment arrays use memory outside the heap, size declarations (DIMENSIONs) and the Reserve intrinsic are not used for the second dimension. Instead, MAlloc, which stands for "memory allocation", is used. This intrinsic calls DOS and requests some memory. MAlloc allocates memory in 16-byte quantities called "paragraphs". For example, here is how to allocate 64000 bytes for a graphic image:

 

        segment char Pixel(1);

        int     I;

        begin

        Pixel(0):= MAlloc(4000);        \4000 paragraphs = 64000 bytes

        I:= 0;                          \(A "for" loop won't work here)

        repeat  Pixel(0, I):= 0;        \Clear the array

                I:= I + 1;

        until I = 64*1000;

      . . .

This provides a segment array that is 1 by 64000. Note that the first dimension is reserved like a normal integer array, and that integers, not bytes, are reserved. The second dimension uses MAlloc, which returns a segment address that points to the start of a 64000-byte block of memory.

 

If we needed a 320K array, a similar process could be used:

 

        segment char Pixel(20);

        int     S, I;

        begin

        for S:= 0, 19 do

                begin

                Pixel(S):= MAlloc(1024);        \1024 * 16 = 16K bytes

                for I:= 0, 16384-1 do Pixel(S, I):= 0;

                end;

        . . .

 

This provides a 20-by-16K array for a total of 320K bytes. Although you could make this a 16K-by-20 array, it is usually better to reserve the large dimension with MAlloc, since this makes better use of the limited (60K) heap space.

 

Segment arrays also can be used for integer and real arrays. For example, here is how a 10-by-4K array of reals is built:

 

        segment real Data(10);

        int     S, I;

        begin

        for S:= 0, 9 do

                begin

                Data(S):= MAlloc((4096*8)/16);

                for I:= 0, 4096-1 do Data(S, I):= 0.0;

                end;

        . . .

 

In the MAlloc statement, 4096 is multiplied by 8 because there are eight bytes in a real number. Note that the total memory allocation is 4096 * 8 * 10 = 320K. This is a large block of memory, and if you have other programs loaded or a limited amount of memory installed, you might not have enough for this array. If DOS is unable to allocate the requested memory, an "OUT OF MEMORY" run-time error occurs.

 

The largest offset that can be used for each type of segment array is:

 

        seg char   $FFFF

        seg int    $7FFF

        seg real   $1FFF

 

SHORT REALS (Advanced)

 

To conserve memory, reals can also be stored in segment arrays in a 4-byte short form. Short reals have a range of ?1.2E-38 to ?3.4E+38 with seven decimal digits of precision. Short reals are only used for storage; they are automatically converted to normal 8-byte reals when fetched. As a result, short reals can be used just like normal reals. Segment arrays of short reals are declared as:

 

        segment short NAME(DIMENSION);

 

For example:

 

        segment short Data(10);

        int     S, I;

        begin

        for S:= 0, 9 do

                begin

                Data(S):= MAlloc((4096*4)/16);

                for I:= 0, 4096-1 do Data(S, I):= 0.0;

                end;

        . . .

 

RELEASING MEMORY (Advanced)

 

A normal array dimensioned or reserved in a procedure only exists as long as the procedure is active. When the procedure returns, the memory used by the array is automatically released. However, a segment array that uses MAlloc does not release memory when the procedure returns. If the procedure is called a second time, more memory is allocated. The Release intrinsic is used to release memory allocated by MAlloc. It requires an argument that is the segment address returned by MAlloc. For example:

 

        proc    Demo;

        segment char Pixel(1);

        begin

        Pixel(0):= MAlloc(4000);

        . . .

        Release(Pixel(0));

        end;

 

It is unnecessary to release memory that is allocated at the global level since the memory is automatically released when the program exits. If you release a block of memory that was not allocated by MAlloc, you get a run-time error. If you write beyond the end of an array, the memory control blocks used by DOS can be corrupted, and you can get a run-time error when you release the memory block. If this occurs, you can find the exact DOS error code by examining the registers in the array returned by GetReg. See DOS call $21, function $49 for details.

 

DIRECTLY ACCESSING MEMORY (Advanced)

 

A segment array can be used to directly access anything in the first megabyte of RAM. It can be used, for instance, to directly access the video memory, or the program segment prefix (PSP) set up by DOS, or the system interrupt vectors. Any segment address can be used, not just the one provided by MAlloc. Here is an example of a segment array used to clear the video text screen (for modes 0-3):

 

        seg int Video(1);

        int     I;

        begin   \Set up for bright white characters on a blue background

        Video(0):= $B800;

        for I:= 0, 2000-1 do Video(0, I):= $1F20;  \attribute:space

        . . .

 

Maze