Article ID: 85571
Article Last Modified on 8/16/2005
APPLIES TO
- Microsoft BASIC Professional Development System 7.0
- Microsoft BASIC Professional Development System 7.1
This article was previously published under Q85571
SUMMARY
This article describes memory management using Microsoft Basic Professional Development System (PDS) version 7.0 and 7.1 for MS-DOS.
The following sections are included:
INTRODUCTION COMPILED WITH NEAR STRINGS COMPILED WITH FAR STRINGS QBX.EXE ENVIRONMENT (FAR STRINGS) DYNAMIC versus STATIC ARRAYS HUGE ARRAYS SIZE LIMITS HOW TO DETERMINE AVAILABLE MEMORY WHAT TO DO WHEN YOUR PROGRAM RUNS OUT OF MEMORY MORE INFORMATION ================ INTRODUCTION ------------ Basic PDS manages memory in three distinct ways: - Compiled with Near Strings: Unless otherwise specified, the default memory model for a compiled program is one using Near string addressing. - Compiled with Far Strings: To compile a program using Far string addressing you must use the /Fs switch. This will allow variable length strings to be stored in multiple segments in Far memory, that is, outside of DGroup. - Interpreted (in the QBX.EXE environment) with Far Strings: This also uses Far string addressing. In addition it implements additional data and code storage techniques unique to operation within the development environment. Each of these three memory models will be discussed in detail later in this document. Each model represents a different way in which program code and data is stored in Random Access Memory (RAM). RAM itself is also divided into three types of memory (see map at and of section): - Conventional MS-DOS Memory (0 up to 640K) This memory is subdivided into three areas: - Low Memory: This is where the Interrupt vector table and MS-DOS code resides. Any TSR programs or device drivers may also be loaded here. On top of that either program code or the QBX environment code will be loaded. - Near Memory (DGroup): This is a single Data segment used by Basic. It can be up to 64K in size. - Far Memory: This is the area of memory from where DGroup stops to the end of the conventional MS-DOS Memory. - 384K Reserved I/O Address Space (Upper Memory Blocks): This is the area of memory between the top of Conventional Memory (640K) and the start of Extended Memory (1024K). This area of memory can contain the following: - BIOS (basic input/output system), ROM chips, and display adapter memory. - Accessory cards such as network cards. - 64K Page frame for Expanded Memory - Extended Memory (XMS) driven by EMM386.EXE and HIMEM.SYS: This is linear memory above 1MB (1024K). Basic only uses the first segment (64K) of extended memory just above the 1MB boundary. This memory area is referred to as the High Memory Area. The QBX environment will load up to 60K of its code into this segment, that is assuming that no other program or portion of a program is loaded here (such as MS-DOS 5.0). - Expanded Memory (EMS) driven by EMM386.EXE: This memory is available through an installable memory board in your system. It can also be acquired using the EMM386 memory driver to access Extended Memory as Expanded Memory. This memory is available to the program as four 16K pages. This memory can be used by the QBX environment for code and data storage, and is used by compiled applications only for code storage (except ISAM) when using overlays provided through the Linker utility. GENERAL MEMORY MAP ------------------ ======================================= <--- | Expanded Memory (EMS) <---| up to 8MB on a PC | (not linear memory) <---| | <---| | ======================================= | | | | 16MB ======================================= | | | | | Extended Memory (XMS) | up to 15 MB on a PC | | | | | | --------------------------------------- | | * High Memory Area 64K * | 1MB --------------------------------------- | | | | ----------------------------- <---| | ------ EMS Page Frame ------- | 384K Reserved ----------------------------- | I/O space ------(four 16K pages)------- | | ----------------------------- <--- | | 640K --------------------------------------- . . Conventional Memory . . 0 ======================================= COMPILED WITH NEAR STRINGS -------------------------- Using "Near Strings" means that variable length strings are referenced using near addressing and thus are stored in Near Memory (DGroup). Near Memory (DGroup) can be up to 64K in size, but after subtracting the space required for Basic's internal variables and stack, only about 46K is left as available for your program. The following is a breakdown of Near Memory (DGroup) and what may be stored here when you use this memory model. The areas of Near Memory include: Static Data, the Stack, the Local Heap, and String Space. Static Data: Data that is of a fixed size and the memory for which is fixed (pre allocated) prior to program execution. - Internal Variables: Variables used by Basic. They maintain information on the current video mode; cursor size and visibility status; the next cursor row and column; MS-DOS version; and more. - String Literals: Such as "Hello" in the code: Print "Hello". - DATA Statements: All information in a DATA Statement, both string and numeric. - Common Data: All Common data, named and blank common, fixed length and variable length data. - Static Arrays: Arrays with dimensions fixed prior to program execution. These arrays can not be REDIMed. - String Constants: Declared with the CONST statement, such as NAME in the code: CONST NAME = "BOB" NOTE: all numeric Constants are stored as code in the code segment. - Scalar Variables (Simple Variables): Non-DIMensioned variables such as X%, Y$, Count#, etc. - User Defined Type Variables: Variables DIMensioned by a TYPE...END TYPE structure. - Fixed Length Strings: String variables DIMensioned to a specific size, i.e. DIM Name as String * 10. Stack: Temporary storage area for certain types of data, such as local variables in Sub Procedures. The size by default is 3K. This can be increased or decreased using the CLEAR statement. For example: CLEAR,,1024 will set the stack size to 1K (1024 bytes). Local Heap: Part of the Near Heap which is a portion of Near Memory (DGroup). The Local Heap contains: - Array Descriptors: All arrays require a descriptor. Single dimension arrays require an 18-byte descriptor, and multiple dimension arrays require an additional 4-bytes for each additional dimension beyond the first. Note that an array of variable length strings is really and array of 4-byte string descriptors (see below). - String Descriptors: Only variable length strings require a descriptor, fixed length strings do not. This 4-byte descriptor describes the string's length and starting location in memory. Note that each variable length string in an array requires a descriptor. - File Data Block Segments: This is where the file buffer is created to store data. You specify the size of this buffer with the LEN clause at the end of an OPEN statement: OPEN "Test.txt" for Random as #1 Len = 512. String Space: Places where variable length string data is stored. In this memory model it is located in Near Memory (DGroup). In other models (Far String model) it will be located in Far Memory. - Variable Length Strings: Only the string data is stored here, not the string descriptors, they are stored in the Local Heap. This will be an important distinction when using the Far String memory model. - Dynamic Variable Length Strings Arrays: As with variable length strings, only the string data is stored here. The string descriptors and array descriptors are stored in the Local Heap. Note this applies to only the Dynamic variable length string arrays. The memory for these arrays is not allocated until runtime. The arrays can be REDIMed. The following is a breakdown of Far Memory and what may be stored here when you use this memory model. The areas of Far Memory include: The Far Heap, Runtime Module Code, and the Communications Buffer. Far Heap: This is the rest of conventional MS-DOS memory up to the 640K boundary after the demands of Low Memory, Near Memory (DGroup) and other components of Far Memory are met. - Dynamic Fixed Element Arrays: Again, only the array data is stored here. The array descriptor is stored in the Local Heap. Dynamic fixed element arrays are composed of elements with a fixed size, such as numbers, user defined types, or fixed length strings. Being Dynamic, these arrays can be REDIMensioned to different lengths but the size of their elements must remain fixed. This is where Huge Arrays are stored (see section on Huge Arrays). Runtime Module Code: Programs compiled without the /O switch require a runtime module (BRUNXX.EXE or BRTXX.EXE). When the program is executed the runtime module is loaded into memory. If the program is compiled with the /O switch, as a stand alone application, the runtime module is not required. Communications Buffer: This is an intermediate storage area for data sent or received from a communications port. The default size is 512K. It can be adjusted from 0 to 32,767 (32K). This is done with the /C:XXXX switch on the compiler, the XXXX being the number of bytes to reserve for the buffer. COMPILED NEAR STRING MEMORY MODEL --------------------------------- MS-DOS ---> -------------------------------------- <--- 640K Communications Buffer | Boundary -------------------------------------- | Runtime Module Code | -------------------------------------- | | Far Memory Far Heap: | Dynamic Fixed-Element Arrays | | | | ---> ====================================== <--- | | | String Space: | | Variable Length Strings | | Dynamic Variable Length String Arrays | | | | -------------------------------------- Near Heap | | | Local Heap: | | Array & String Descriptors | | File Data Block Segments | | | | -------------------------------------- <--- Data Group Stack (DGroup)-------------------------------------- Near Memory | Static Data: | Internal Variables | String Literals | Data Statements | Common Data | Static Arrays | Constants | Scalar Variables | User Defined Type Variables | Fixed Length Strings | ---> ====================================== <--- | Program Code | Low Memory -------------------------------------- | MS-DOS | -------------------------------------- <--- COMPILED WITH FAR STRINGS ------------------------- Using "Far Strings" means that variable length strings are referenced using far addressing and thus can be stored in Far Memory. The following is a break down of Near Memory and what may be stored here when you use this memory model. It is pretty much the same as the Near Memory model when using Near Strings except for the following: Static Data: Only fixed length Common Data. (Common data for variable length strings is stored in Far Memory). The following is a break down of Far Memory and what may be stored here when you use this memory model. String Space is now located in Far memory for variable length strings. It is referred to as the String Segments. Note, fixed length strings and Static arrays are still stored with all other Static Data in DGroup as before: String Segments: - Local Dynamic String Array Segments: Data of Dynamic string arrays created within a Sub Procedure or Function is stored in a segment of this type. A new segment is allocated for each Sub or Function. The segment can be up to 64K in size. - Module Level String and Named Common String Segment This is an independent segment up to 64K in size. This segment stores all data from variable length strings and string arrays created in the main module. It also contains data from variable length strings and string arrays in Named Common blocks. - Blank Common String Segment: This is another independent segment up to 64K in size. This segment contains data from variable strings in Blank Common blocks. - Local and Temporary String Segment: This is another independent segment up to 64K in size. This segment is shared by all procedures and module level code. In it is stored all data for local and temporary variable length strings created in a given program. Far Heap: The File Data Block is now located here. COMPILED FAR STRING MEMORY MODEL -------------------------------- MS-DOS --> -------------------------------------- <--- 640K Communications Buffer | Boundary -------------------------------------- | Runtime Module Code | -------------------------------------- | | Far Heap: | Dynamic Fixed-Element Arrays | File Data Block Segments | Far Memory -------------------------------------- | | String Segments (Variable Length Strings): | Local & Temp. String Segment | Module Level String & | Named Common String Segment | Blank Common String Segment | Local Dynamic String Array Segments | | --> ====================================== <--- | | | Local Heap: Near Heap | Arrays & String Descriptors | | | | -------------------------------------- <--- | Stack | -------------------------------------- Data Group (DGroup) Static Data: (Near Memory) Internal Variables | String Literals | Data Statements | Fixed Length Common Data | Static Arrays | Constants | Scalar Variables | User Defined Type Variables | Fixed Length Strings | --> ====================================== <--- | Program Code | Low Memory -------------------------------------- | MS-DOS | -------------------------------------- <--- QBX.EXE ENVIRONMENT (FAR STRINGS) --------------------------------- The use of Far String addressing is the default in the QBX.EXE environment. Although memory usage and data storage does differ considerably from compiled versions of the same program. The following is some information that applies uniquely to the QBX (QuickBasic Extended) environment: Conventional Memory: QBX requires 315K + additional memory needed for Quick libraries if loaded. Extended Memory: If you have a 64K block of High memory (Extended Memory) and nothing else is loaded there (such as MS-DOS 5.0) then QBX will automatically load 60K of its code here (assuming HIMEM.SYS is loaded). This reduces the amount of conventional memory required by the QBX environment to 255K. Expanded Memory: If expanded memory is available it will be used by the QBX.EXE environment to store some of your program's code. Only Sub and function procedures greater than 1K and less than 16K will be stored here. Subprograms larger than 16K are stored in the far heap in conventional memory in both Basic 7.0 and 7.1. QBX.EXE from version 7.1 uses expanded memory more efficiently than QBX.EXE from version 7.0. In 7.0, each subprogram from 1K to 16K in size uses a full 16K of expanded memory. In 7.1, subprograms smaller than 16K will use expanded memory in 1K chunks. In 7.1, if a subprogram is 2K in size, it will use only 2K of expanded memory. In Basic 7.0, a 1K Sub as well as a 15K Sub will each require a 16K chunk of expanded memory. To save memory in Basic 7.0, you should try to keep the size of your procedures smaller than 16K but as close to 16K as possible. In Basic 7.1, you should simply try to keep sizes less than 16K. You can determine the size of your procedures from within the environment by pressing <F2>. The number you see to the right of each module name is the module's size. NOTE: When you have one or more Sub or Function procedures, QBX will require a one time overhead of two 16K blocks (32K) for its own internal tables. QBX.EXE Switches for Managing Memory: /Ea: This will tell QBX to store fixed length arrays in expanded memory. These arrays must be between 512 and 16K in size. DO NOT use this switch in combination with /Es. /Es: Use this switch when you have some mixed language routines in a quick library that access expanded memory. If you do not use this switch the memory may not be available to those routines when they are called. DO NOT use this switch in combination with /Ea. /E:n This will specify the amount of expanded memory to reserve for use by QBX. If this switch is not used the default is all available expanded memory. If /E:0 is used then no expanded memory will be used and the /Ea and /Es switches are overridden. /NOFrills This switch reduces the functionality of the environment and frees up to 19K more memory that the QBX environment would otherwise use. - No Utility menu and associated commands. - No Options menu and associated commands. - No Help menu and associated commands. Other QBX.EXE notes: - The code of QBX.EXE itself is located in Low memory. - If a Quick library is loaded, its code is loaded in Far memory. The Quick library's Scalar variables, Static arrays, and Common variables are stored in DGroup. - Program code is now located in the Far Heap. - Static arrays (except those in Common Blocks) are now stored in the Far Heap. - Every Common Block has a variable table created for it of a corresponding size. This variable table is created for every module that this Common Block is listed in. QBX ENVIRONMENT FAR STRINGS --------------------------- MS-DOS --> -------------------------------------- <--- 640K Communications Buffer | Boundary -------------------------------------- | Quick Library Code | -------------------------------------- | | Far Heap: | Dynamic Fixed Element Arrays | File Data Block Segments | User Program Code Far Memory Static Arrays (not in Blank Common) | | -------------------------------------- | | String Segments (Variable Length Strings): | Local & Temp. String Segment | Module Level String & | Named Common String Segment | Blank Common String Segment | Local & Dynamic String Array Segments | | --> ====================================== <--- | | | Local Heap: | | Array & String Descriptors | | String Literals | | Data Statements | | Fixed Length Common Data Near Heap | Static Arrays | | Constants | | Scalar Variables | | User Defined Type Variables | | Fixed Length Strings | | | | -------------------------------------- <--- Data Group Stack (DGroup) -------------------------------------- (Near Memory) | Static Data: | Quick Lib Scalar Variables | Quick Lib Static Arrays | Quick Lib Common Variables | Internal Variables | Blank Common Static Arrays | --> ====================================== <--- QBX Code | -------------------------------------- Low Memory MS-DOS | -------------------------------------- <--- DYNAMIC versus STATIC ARRAYS ---------------------------- A Dynamic Array is one whose size is defined at run time. This type of array can be REDIMensioned during the execution of the program. A Static Array is one whose size is defined at compile time and cannot be changed at run time. To make an array, Dynamic do any of the following: - Use the REDIM statement when first creating the array: REDIM A(20) AS STRING * 80 - Use a simple variable for at least one of the dimensions when DIMensioning the array: d = 20 DIM A(d,64) AS INTEGER - Use the array in a COMMON statement and then DIMension it (Dynamic arrays should *always* be placed after the COMMON statement): COMMON A() DIM A(64) - Use the REM $DYNAMIC metacommand before DIMensioning the array: '$DYNAMIC DIM A(64) To make an array Static do any of the following: - DIMension the array with a literal: DIM A(64) AS INTEGER - DIMension the array with a CONSTant: CONST Num = 16 DIM A(Num) AS INTEGER - Implicitly DIMension the array (just start assigning values to elements of an array). Note, when implicitly DIMensioning an array you are limited to a default of 10 subscripts: A%(0) = 5 A%(1) = 6 A%(2) = 7 - Use the REM $STATIC mettacommand: '$STATIC DIM A(64) HUGE ARRAYS ----------- Huge arrays are arrays that are greater than 64K in size. These huge arrays must be dynamically DIMensioned and must contain elements of a fixed size. In order to use huge arrays you must use the /AH option for the QBX environment and/or for the Compiler. To DIMension an array Dynamically you can either REDIM the array when first created, use the REM $DYNAMIC mettacommand, DIMension the array with a variable, or DIMension the array after a COMMON Statement in which you reference that same array. (See previous section on Dynamic versus Static arrays). An array of "Fixed-elements" refers to an array whose elements are any numerical type (Integer, Long, Single, Double, Currency), an array whose elements are fixed length strings (A as String * 16) or an array whose elements are a user defined type (NOTE: a user defined type can not contain variable length strings). If the array is greater than 128K, the size of each element of the array must be a power of two (such as 2, 4, 8, 16, 32, 64, 128, 256, and so forth). Since all numerical types have a size that is a power of two, all you need to consider are elements that are either fixed length strings or user defined types. With a user defined type, it is not important for each field in the type to be a power of two in size, but rather for the total size of the type be a power of two. For example: Type testtype x as integer y as long End Type This user defined type has a size of 6 bytes, which is not a power of two. You must "pad" the type with additional bytes: Type testtype x as integer y as long dmmy as string * 2 End Type Total size is now 8, which is a power of two (2^3). The field used for padding can be any type, but a fixed length string is especially convenient for padding. The above padding is necessary in huge arrays because an array element may not span a segment boundary. That is, each element of an array must fit in memory in such a way that they completely fit within a segment's boundary (aligned with the segment boundary) with no leftover space or gap. For example, if we have an element 32K in size, two elements will fit evenly in a single segment (with no gaps). But if we have an element 31K in size, two elements will fit in one segment but others will have to start in another segment, leaving a 2K gap in memory. | 2k | element1 | element2 || element3 | element4 | 2k | The segment boundary is between element2 and element3. In order for the elements to be aligned with the segment boundary, Basic automatically shifts the first two elements in memory. Note the 2K gaps and the fact that no more records can be boundary-aligned after element4. Because of this requirement of boundary alignment, if the elements of a huge array are not a power of two in size, the largest size this huge array could be is under 128K. Otherwise, if the elements are a power of two and thus able to be segment boundary aligned, the size of the huge array is limited only by available conventional far memory (MS-DOS 640K). SIZE LIMITS ----------- Data Types Maximum Minimum --------------------------------------------------------------------- Variable names 40 characters 1 character String length ($) 32,767 characters 0 character [1 byte per char] Integers (%) 32,767 -32,767 [2 bytes] Long Integers (&) 2,147483,647 -2,147,483,648 [4 bytes] Currency (@) 922,337,203,685,477.5807 -922,337,203,685,477.5808 [8 bytes] Single-precision (!) [4 bytes] Pos 3.402823E+38 1.401298E-45 Zero 0 Neg -1.401298E-45 -3.402823E+38 Pos with /Fpa 3.402823E+38 1.175494E-38 Zero with /Fpa 0 Neg with /Fpa -1.175494E-38 -3402823E+38 Double-precision (#) [8 bytes] Pos 1.797693134862315D+308 4.940656458412465D-324 Zero 0 Neg -4.940656458412465D-324 -1.797693134862315D+308 Pos with /Fpa 1.79769313486232D+308 2.2250738585072D-308 Zero with /Fpa 0 Neg with /Fpa -2.2250738585072D-308 -1.79769313486232D+308 Arrays Maximum Minimum ---------------------------------------------------------------------- Array size (all elements) Static 65,535 bytes (64K) 1 Dynamic Available Memory (MS-DOS 640K) 0 Array Dimensions 8 1 Array subscripts 32,767 -32,768 Files & Procedures Maximum Minimum --------------------------------------------------------------------- Number of arguments 60 interpreted 0 Nesting of include files 5 levels 0 Procedure size (interpreted) 65,535 bytes (64K) 0 Module size (compiled) 65,535 bytes (64K) 0 Overlay size 128K 0 Data Files open simultaneously 255 0 Data file record number 2,147,483,647 1 Data file record size(bytes) 32,767 (32K) 1 Data file size Available disk space 0 Path names 127 characters 1 HOW TO DETERMINE AVAILABLE MEMORY --------------------------------- There are two Basic Functions you can use to find out how much of certain kinds of memory is available. These are the FRE Function and the STACK Function. The value returned by FRE is determined by the argument passed to it, as well as weather or not you are using Far String addressing. Here are some examples: Function Output with Near Strings Output with Far Strings ---------------------------------------------------------------------- FRE(A$) Unused space in DGroup Unused space in segment where A$ is stored. FRE("") Unused space in DGroup Unused space for temporary strings. FRE(-1) Total unused memory Total unused memory FRE(-2) Unused Stack space Unused Stack space FRE(-3) Available EMS Available EMS (expanded memory) (expanded memory) NOTE: FRE(-2) returns the amount of Stack space never used by the program, sort of like a high water mark for the Stack. The value returned will get smaller as you program uses more of the Stack. If the amount of Stack space being used decreases, the value returned by this function will not change. The value of FRE(-2) may not be accurate when used in the immediate window of the QBX environment. The STACK function returns the maximum Stack size that can be allocated. This is a combination of the unused Stack space of the current Stack setting as well as the rest of the unused space in DGroup. To obtain the amount of free DGroup memory when using Far String addressing use the following equation: FreeDG& = STACK - FRE(-2) WHAT TO DO WHEN YOUR PROGRAM RUNS OUT OF MEMORY ----------------------------------------------- In General: - Reduce size of your MS-DOS buffers (in CONFIG.SYS) - Eliminate any terminate-and-stay-resident (TSR) programs - Eliminate non-essential device drivers under MS-DOS. - If in the QBX environment, unload any files that are not needed for your program. - Make your program smaller. - If you have Huge arrays (>64K) make sure to use the /Ah option (see previous section on Huge arrays). - Make sure your arrays have elements of a fixed size. - When compiling with BC.EXE, do not use /D if possible. - When linking use stub-files whenever possible. (pg. 538-539 "Basic 7.0: Programmer's Guide" for 7.0/7.1) . - Use a smaller File buffer in the OPEN statement's LEN clause. - Use Link Overlays. (pg. 612-614 "Basic 7.0: Programmer's Guide"). DGroup Memory: You may be out of space in DGroup even if you still have space left in Far memory and/or extended memory. Use the following statement to find out how much DGroup you have left: PRINT STACK - FRE(-2) - Reduce stack size to the minimum that your program requires (Using the CLEAR Statement, see manual). - Use Fixed Length strings whenever possible - Use fewer string literals and DATA statements, read data in from a file. - Use DEFINT A-Z at the top of your modules. This makes the default data type integer. - Declare your arrays as DYNAMIC (see previous section on Dynamic versus Static). - Use Far sting option (/Fs) - If in the environment, put your Functions and Procedures into a Quick library. - NOTE: within the QBX environment every Common block has a variable table created for it of a corresponding size. This variable table is created for every module that this Common block is listed in. So do not list a Common block in a module unless you need it to be there. Extended Memory: Only the QBX environment will take advantage of Extended memory. - Make sure it is available. Load QBX without the extended memory driver. Execute PRINT FRE(-1) in the Immediate window, note the value returned, exit QBX. Install the extended memory driver. Load QBX and execute PRINT FRE(-1) as before. If extended memory is available the second value should be about 60K greater than the first. Expanded memory: Only the QBX environment and a compiled program using Overlays will take advantage of Expanded memory. - Make sure expanded memory is available. Load QBX and execute PRINT FRE(-3) in the immediate window, which should return the amount of available Expanded memory. If it returns an error "Feature unavailable", then you either do not have expanded memory or the driver is not loaded or loaded incorrectly. Check the "Getting Started" manual under "Memory Management of QBX" for more information.
Additional query words: BasicCom 7.00 7.10 EMM386.EXE EMM386.SYS
Keywords: KB85571