Microsoft KB Archive/45850
Article ID: 45850
Article Last Modified on 8/16/2005
- Microsoft BASIC Professional Development System 7.0
- Microsoft BASIC Professional Development System 7.1
- Microsoft QuickBasic 4.0
- Microsoft QuickBASIC 4.0b
- Microsoft QuickBasic 4.5 for MS-DOS
- Microsoft Visual Basic for MS-DOS
This article was previously published under Q45850
Programmers may want to understand how Microsoft QuickBasic and Microsoft Basic Compiler arrange memory in order to write programs that make efficient use of system resources. The QuickBasic language provides a variety of data types and modular code constructs that allow you to manage the data and code of your programs.
This article covers the following topics:
- Code management
- Modular programming
- What goes at the module level in a support module
- COMMON, COMMON SHARED, SHARED, DIM SHARED
- Data management
- $DYNAMIC and $STATIC metacommands
- Huge arrays
Appendix A contains a code example to illustrate the topics covered in this article.
Appendix B explains the .MAP file created when the Appendix A program example is linked. You can use the LINK map to determine how close each module is to approaching the 64K code limit and how close the .EXE program's static variables are getting to the 64K limit in the DGROUP.
Appendixes C and D describe in detail the memory mapping for running programs in the following three different environments: the QB.EXE editor, a compiled .EXE program using the run-time module, and an .EXE compiled with the stand-alone option.
A "module" is defined as an individual source file containing Basic procedures.
"Module-level code" is defined as the statements within a module that are outside a SUB...END SUB, FUNCTION...END FUNCTION, or DEF FN..END DEF definition block.
A "support module" is a source file, separate from the main module, that contains additional SUB or FUNCTION procedures.
Modular programming deals with splitting programs into separate modules (i.e., separate source files containing SUB or FUNCTION procedures). The reasons for doing this are the following:
- Once you have written a block of code as a module, it can easily be incorporated into future projects.
- Debugging is easier. Problems can be located much more quickly and can be contained in a specific area of the program.
- The compiler (BC.EXE) and the environment (QB.EXE) have a 64K code limitation per module. A modular style of programming allows you to create programs larger than 64K, since every module, even though it is part of one program, can be up to 64K in size.
If a program is large, it may be necessary to break it up into several modules. This is easily accomplished by breaking SUB or FUNCTION procedures out of the main module and placing them in support modules. These modules are then compiled separately with BC.EXE and LINKed with the main module as in the following example.
Consider a program Main.BAS, which is broken into three modules:
- MAIN.BAS (which calls external procedures)
- MODULE2.BAS (which contains SUB and/or FUNCTION procedures)
- MODULE3.BAS (which contains SUB and/or FUNCTION procedures)
The three modules are then each compiled separately to produce the following object files:
- BC MAIN.BAS; ----> MAIN.OBJ
- BC MODULE2.BAS; ----> MODULE2.OBJ
- BC MODULE3.BAS; ----> MODULE3.OBJ
To produce the executable (.EXE) program, the following command line is used:
LINK Main.OBJ+Module2.OBJ+Module3.OBJ; ----> Main.EXE
Main.EXE is the finished executable program. When you compile in QuickBasic's environment (QB.EXE), the environment automatically compiles each module and LINKs them together to produce the same .OBJ files and finished executable program.
To make an .EXE program from the QB.EXE environment, do the following:
- Choose the Run menu.
- Choose the Make EXE File... option.
- Choose the Stand-Alone EXE File option to create a stand-alone file. If this option is not selected, the program requires that the BRUN4x.EXE run-time module be present at run time.
- Press the ENTER key.
The QuickBasic environment gives you an easy way to do the following operations, which are described further below:
- Create a new Module for SUBs
- Edit SUBs and Modules
- Delete SUBs
- Move SUBs from one Module to another Module
In QB.EXE, to create a separate module (source file) for SUBs, do this:
- Choose the File menu.
- Choose the Create File... option.
- Enter the name for the module.
- Press the ENTER key.
A file will then be created with the name you specified, and it will be in memory with the other module(s). To save all of the loaded modules, do the following:
- Choose the File menu.
- Choose the Save All option.
When the modules are saved together, QuickBasic creates a file (with the extension .MAK) that keeps track of the main module and the other modules that are used by the main. To load all the modules in at once, do the following:
- Choose the File menu.
- Choose the Open Program... option.
- Select the main module of program as the file to be opened.
To view and select SUBs for editing, do the following:
- Choose the View menu.
- Choose the SUBs... option.
- Highlight the module or SUB that you want to edit.
- Tab down to Edit in Active, or Edit in Split.
- Press the ENTER key.
To delete a SUB, do the following:
- Choose the View menu.
- Choose the SUBs... option.
- Highlight the SUB you want to delete.
- Tab down to the Delete option.
- Press the ENTER key.
To move a SUB to a different module, do the following:
- Choose the View menu.
- Choose the SUBS... option.
- Highlight the SUB you want to move.
- Tab down to the Move option.
- Press the ENTER key.
- Select the module you want the SUB to be in.
- Press the ENTER key.
For more information about using the QB.EXE environment, please see the "Microsoft QuickBasic: Learning to Use" manual for version 4.50, or see the "Microsoft QuickBasic 4.0: Learning and Using" manual for versions 4.00 and 4.00b and the Basic compiler versions 6.00 and 6.00b.
What Goes at the Module Level in a Support Module
A support module, when compiled, produces an object (.OBJ) file that is LINKed with a main module to create an executable file. The module-level code of the main module is the only code directly executed by QuickBasic. The module-level code of the support modules cannot be CALLed, RUN, or CHAINed. This module-level code of the support modules is used only for:
- Event- or error-trapping handlers
- TYPE definitions, DIM, and COMMON SHARED statements
You can place ON Event GOSUB and ON ERROR GOTO trapping statements within SUBprogram procedures [where ON Event means ON KEY(n), ON COM(n), ON TIMER(n), etc.]. However, the line or line label that is the target of the event or error trap's GOTO or GOSUB must be at the module-level code in the same module as that SUBprogram. This is because QuickBasic doesn't allow GOSUBs or GOTOs from a SUBprogram to other modules or SUBprograms, and QuickBasic allows only ON Event GOSUB and ON ERROR GOTO statements to jump from a SUBprogram to the module-level code.
The compiler metacommands (REM $INCLUDE, REM $STATIC, and REM $DYNAMIC) can also be used at the module level. REM $INCLUDE pastes extra source into the module at compile time. REM $DYNAMIC and REM $STATIC declare subsequent arrays as $DYNAMIC (allocated at run time) or $STATIC (allocated at compile time).
You can use TYPE...END TYPE, DIM, and COMMON SHARED statements in the module-level code. Variables and arrays can be shared between modules using the COMMON SHARED statement at the module level.
COMMON, COMMON SHARED, SHARED, and DIM SHARED
The SHARED statement gives a SUB or FUNCTION procedure access to variables declared at the main module level of that module (without passing them as parameters). It does NOT give access to variables declared in support modules.
The COMMON statement makes variables available at the module level between modules. The SHARED attribute for the COMMON statement (i.e., COMMON SHARED) is required to share the variables with SUBprograms or FUNCTIONs. The list of variables in the COMMON and COMMON SHARED statements must match in type in the COMMON and COMMON SHARED statements in each module.
When using the COMMON (or COMMON SHARED) statement, static and dynamic arrays are dimensioned differently. Static arrays in COMMON must be DIMensioned BEFORE the COMMON statement in all the modules with that COMMON statement. Dynamic arrays in COMMON must be DIMensioned AFTER the COMMON statement in just the main module and should not be DIMensioned in any support modules.
There are two differences between using the SHARED statement and the DIM SHARED statement:
- To make a variable accessible by all the SUBprograms in a module, use DIM SHARED at the module level.
- To share a module level variable with a specific SUBprogram, put the variable in a SHARED statement in the SUBprogram. The SHARED statement goes directly after the first line of the SUBprogram.
$DYNAMIC and $STATIC Arrays
The default setting for storing arrays is REM $STATIC, which stores all arrays in DGROUP (the default data segment). For any Basic .EXE program, DGROUP is limited to 64K. If you are using static arrays, you can get more memory in DGROUP by making the arrays dynamic, which moves them into the far heap. To make an array dynamic, DIMension the array after the metacommand REM $DYNAMIC, or DIMension the array with a variable in its subscript.
All dynamic arrays are stored on the far heap except arrays of variable- length strings. Strings, variable-length string arrays, and simple variables are always stored in DGROUP. To store strings in the far heap, you must DIMension a dynamic array of fixed-length strings. DIMensioning strings as fixed length in dynamic arrays helps to free up storage space that they otherwise could have taken in DGROUP, which is limited to 64K.
Huge arrays are arrays that are larger than 64K. When using huge arrays, you must invoke the QB.EXE editor and BC.EXE compiler with the /AH option. The huge array must be DIMensioned as a dynamic array, either with a variable in the array subscript or with the preceding metacommand REM $DYNAMIC. The /AH option allows dynamic arrays of user-defined types, fixed- length strings, and numeric data to occupy all of available memory.
The number of bytes in a single element of a huge array should preferably be a power of 2, in order to avoid wasted memory and to enable arrays to be larger than 128K, as explained below.
Space is allocated for a huge array contiguously in the far heap, with the restriction that no single array element (or record) is allowed to be split across a 64K boundary. If a record size is not a power of 2, the array is allocated at an offset high enough, relative to the array's base segment address (returned by the VARSEG function), such that no array element is split across the boundary at exactly 64K above the base segment. The value returned by the VARPTR function for the first element of the array then indicates both the offset of the array, and also the size of a gap created in far heap. This gap fragments the far heap, and is wasted, unused memory. The size of the gap is equal to (65,536) MOD (array record size). [In the worst case, the gap can be up to (array record size) minus 1 in size.]
A "Subscript out of range" error occurs when allocating a huge array larger than 128K if the array elements have a size that is not an even power of 2. Arrays larger than 128K must have an element size that is a power of 2 (2, 4, 8, 16, 32, 64, etc.), since arrays are stored contiguously and no single array element is allowed to be split across a 64K boundary.
The final major aspect of QuickBasic memory management is that QuickBasic attempts to make efficient use of memory, which may cause variable-length strings, variable-length string arrays, and dynamic arrays to move around in memory from statement to statement at run time. (Other variables have a fixed location determined at EXE load time.) In addition, if you perform a CHAIN statement, then any data passed in a COMMON block may move. This means that the results of the VARPTR, VARSEG, VARPTR$, or SADD function should always be used immediately after that function is invoked.
APPENDIX A: SAMPLE PROGRAM
This sample program demonstrates the topics covered in this article. This program gives an example of ways to DIMension arrays, and to pass variables between modules and SUBprograms. The program consists of two modules, each which defines one SUBprogram.
Main Module (EXAMPL1.BAS)
DECLARE SUB OtherModSub () DECLARE SUB Subdemo () TYPE PowerOfTwo I AS INTEGER ' 2 Bytes L AS LONG ' 4 Bytes S AS SINGLE ' 4 Bytes D AS DOUBLE ' 8 Bytes St AS STRING * 14 ' 14 Bytes END TYPE ' 32 Bytes Total, a power of 2, 2^5 REM $STATIC DIM A(100) AS INTEGER ' Static array, stored in DGROUP. COMMON SHARED A() AS INTEGER ' Share the variables with the other COMMON SHARED B AS STRING * 20 ' module and its SUBprograms. COMMON SHARED X() AS INTEGER REM $DYNAMIC DIM X(100) AS INTEGER ' Dynamic array, stored in far heap. DIM PTwo(4000) AS PowerOfTwo ' Dynamic huge array, stored in far heap ' Each element is a power of two, 2^5. DIM NonFixedLen(10) AS STRING ' Dynamic array, but since it is not ' fixed length it is stored in DGROUP. REM $STATIC DIM SHARED M(100) AS LONG ' These variables are shared with all DIM SHARED Equals AS STRING * 10 ' the SUBprograms in this module, ' and are stored in the DGROUP. Num = 40 DIM DynArray(Num) AS SINGLE ' The array is dynamic since it is ' DIMensioned with a variable, so ' it is stored in far heap. Plus$ = " + " Equals = " = " M(1) = 7 A(1) = 4 CALL Subdemo ' CALLs the SUBprogram of this module. CALL OtherModSub ' CALLs the SUBprogram of the support module. END 'SUBprogram of the Main Module: SUB Subdemo SHARED Plus$, NonFixedLen() AS STRING, Num PRINT STR$(Num) + Plus$; PRINT STR$(M(1))+ Equals + STR$(A(1)); A(1) = M(1) END SUB
Support Module (EXAMPL2.BAS)
REM $STATIC DIM A(100) AS INTEGER ' Only the static array is DIMensioned COMMON SHARED A() AS INTEGER ' before the COMMON SHARED statement. COMMON SHARED B AS STRING * 20 COMMON SHARED X() AS INTEGER ' The dynamic array is only DIMensioned ' in the main module. Panic: ' The label for ON ERROR has to be at resume next ' the module level. ' SUBprogram of the Support Module: SUB OtherModSub PRINT A(1) ON ERROR GOTO Panic END SUB
APPENDIX B: LINK .MAP FILE EXPLANATION
Below is a .MAP file created when the program example in Appendix A is linked. The .MAP file from a successful link shows how close a module's code segment is approaching the 64K code limit, and how close the static variables for the entire .EXE program are to approaching the 64K limit in the DGROUP. To generate a listing map of a program, include a map filename in the third argument of the LINK command, as in the following example:
Notes for the link .MAP shown further below are as follows:
- EXAMPL1_CODE is the code segment for EXAMPL1.BAS.
- EXAMPL2_CODE is the code segment for EXAMPL2.BAS.
- The code segment of each module has to be less than 64K. When the code segment is getting close to 64K, break the module into two or more modules. To find the length of a code segment, look under the Length column. For example, the length of the code segment for EXAMPL1.BAS is 15A hex bytes, which is 346 in decimal notation.
- FAR_MSG is the far management group that holds text of error messages. This is NOT the far heap. The far heap is allocated at run-time.
- According to the Origin section at the bottom of the link map, DGROUP starts at 06DC:0, which is the paragraph address 06DC hex, with an offset of 0. This is the same as the byte address 06DC0 hex (since a paragraph contains 16 bytes). Thus, in this example, DGROUP starts where BR_DATA starts.
- DGROUP ends at the Stop address of the STACK.
- To find out how large the program's DGROUP is, take the Stop address of the stack and subtract from it the Start address of the first data element in the DGROUP. For example, the size of the program's DGROUP is 7F9F hex minus 6DC0 hex, which equals 11DF hex (4575) bytes.
- All addresses in the .MAP are only relative to the start of the .EXE program. The absolute load address is determined by DOS only at run time. The VARSEG and VARPTR statements can be used in your program at run time to display the absolute addresses of the variables.
Start Stop Length Name Class 00000H 00159H 0015AH EXAMPL1_CODE BC_CODE 00160H 001C1H 00062H EXAMPL2_CODE BC_CODE 001C2H 03931H 03770H CODE CODE 03932H 03A0CH 000DBH INIT_CODE CODE 03A10H 041B2H 007A3H _TEXT CODE 041C0H 065EFH 02430H EMULATOR_TEXT CODE 065F0H 065F0H 00000H C_ETEXT ENDCODE 065F0H 065F7H 00008H FAR_HDR FAR_MSG 065F8H 06C44H 0064DH FAR_MSG FAR_MSG 06C45H 06C46H 00002H FAR_PAD FAR_MSG 06C47H 06C47H 00001H FAR_EPAD FAR_MSG 06C50H 06DBFH 00170H EMULATOR_DATA FAR_DATA 06DC0H 06DC0H 00000H BR_DATA BLANK 06DC0H 06DEFH 00030H BR_SKYS BLANK 06DF0H 06EFBH 0010CH COMMON BLANK 06EFCH 070E3H 001E8H BC_DATA BC_VARS 070E4H 070E9H 00006H NMALLOC BC_VARS 070EAH 070EAH 00000H ENMALLOC BC_VARS 070EAH 070EAH 00000H BC_FT BC_SEGS 070F0H 0710FH 00020H BC_CN BC_SEGS 07110H 07122H 00013H BC_DS BC_SEGS 07124H 07124H 00000H BC_SAB BC_SEGS 07124H 0712BH 00008H BC_SA BC_SEGS 0712CH 0712FH 00004H BC_SAE BC_SEGS 07130H 07345H 00216H _DATA DATA 07346H 0761FH 002DAH _BSS DATA 07620H 0771CH 000FDH BR_DATA DATA 0771EH 0771EH 00000H XIB DATA 0771EH 07741H 00024H XI DATA 07742H 07742H 00000H XIE DATA 07742H 0774DH 0000CH DBDATA DATA 0774EH 0775BH 0000EH CDATA DATA 0775CH 0775CH 00000H XIFB DATA 0775CH 0775CH 00000H XIF DATA 0775CH 0775CH 00000H XIFE DATA 0775CH 0775CH 00000H XPB DATA 0775CH 0775CH 00000H XP DATA 0775CH 0775CH 00000H XPE DATA 0775CH 0775CH 00000H XCB DATA 0775CH 0775FH 00004H XC DATA 07760H 07760H 00000H XCE DATA 07760H 07760H 00000H XCFB DATA 07760H 07760H 00000H XCF DATA 07760H 07760H 00000H XCFE DATA 07760H 0779FH 00040H CONST DATA 077A0H 077A0H 00000H BC_DATA BC_DATA 077A0H 077A0H 00000H XOB BSS 077A0H 077A0H 00000H XO BSS 077A0H 077A0H 00000H XOE BSS 077A0H 07F9FH 00800H STACK STACK Origin Group 06DC:0 DGROUP 065F:0 FMGROUP Program entry point at 03A1:00C8
APPENDIX C: WHERE QuickBasic 4.00, 4.00b, AND 4.50 STORE THEIR ARRAYS
There is one difference in array storage between programs run as compiled .EXE files and programs run within the QuickBasic versions 4.00, 4.00b, and 4.50 environment (QB.EXE). In the QB.EXE environment, an array that is static, not in COMMON, and not composed of variable-length strings, is stored in the far heap (instead of DGROUP, as in .EXE programs).
This changes memory management in your program, depending on where you run your program.
Thus, in programs run within the QuickBasic 4.00, 4.00b, or 4.50 environment (QB.EXE), arrays are stored as follows:
- All $STATIC arrays in COMMON are stored in DGROUP and can be referenced with near addresses.
- All arrays of variable-length strings are also stored in DGROUP and can also be referenced with near addresses.
- All other arrays are stored as far objects and require far addresses. This includes $STATIC arrays that are not in a COMMON statement, and these arrays can move in memory like $DYNAMIC arrays.
In QuickBasic 4.00, 4.00b, and 4.50 programs that are run as compiled .EXE files, arrays are stored as follows:
- All $STATIC arrays are stored in DGROUP and can be referenced with near addresses.
- All $DYNAMIC arrays of variable-length strings are also stored in DGROUP and can also be referenced with near addresses.
- All other $DYNAMIC arrays are stored as far objects.
APPENDIX D: MEMORY MAPS
This appendix contains one general memory map and three detailed run-time memory maps.
General Memory Diagram
The following diagram summarizes how QuickBasic compiled programs are loaded into memory at run time:
High Memory (640K maximum) +-----------+ | | The far heap stores dynamic arrays. The far | (Far) | heap consists of the rest of high memory | Heap | available after MS-DOS and the Basic | | program's DGROUP segment and code --- +-----------+ segment(s) are allocated. | | | The stack is used to store temporary data, | | Stack | such as variables that are passed to a DGROUP | | subprogram procedure. The default stack Up to |- - - - - -| size is 2K. 64K | | The DGROUP (default data segment) is used | | (Near) | to store all static arrays, strings, | | Data | simple variables, and the stack. DGROUP --- +-----------+ can be up to 64K in size. : +-----------+ | Code | The code segments store the executable code. | Segments | Each module can have up to 64K for its +-----------+ code segment. Low Memory
The following Figures 1, 2, and 3 describe in more detail the arrangement of code and data in memory at run time.
The first figure (below) shows the run-time memory map when the program is executed within the QB.EXE version 4.x environment:
+-------------+ | Quick | | Library | +-------------+ |Communication| User-specified size | buffers | +-------------+ | | This area contains items, such as | FAR heap | large/huge arrays and user code, | | dynamic arrays. +-------------+ User Data ------->Run-time heap| Files buffers, etc. End DS:xxxx +-------------+ | String heap | +-------------+ | User Program| DGROUP | Data | +-------------+ UP to | User Stack | 64K +-------------+ | Quick Lib | | Data | +-------------+ | QuickBasic | User Data | Static | Start DS:0 ------->| Data | +-------------+ | QuickBasic | QB.EXE Low Memory ------->| Code | +-------------+ | MS-DOS | MS-DOS Operating System +-------------+ 0000:0000 ------->
Figure 1 (above): The Memory Map for QB.EXE 4.x Environment
This second figure (below) shows the run-time memory map as it appears when the run-time module BRUN4x.EXE is used with the separate compilation (Make EXE File...) method:
+-------------+ | BRUN4x.EXE | Separately loaded run-time code +-------------+ |Communication| User-specified size | buffers | +-------------+ | | This area holds less-frequently- | | used items, such as dynamic | FAR heap | arrays, the user environment | | table. | | | | +-------------+ User Data ------->Run-time heap| End DS:xxxx +-------------+ | String heap | +-------------+ | User Stack | Preset to 2K +-------------+ | Named | Named COMMON areas | COMMON | DGROUP +-------------+ | BC_DATA | User program variables Up to +-------------+ 64K | BC_CONST | User program constants +-------------+ | Blank COMMON| +-------------+ | _DATA | QuickBasic run-time data areas, User Data | CONST | used during user code execution Start DS:0 ------->| _BSS | +-------------+ | User Code | User program separately linked Low Memory ------->| | with BRUN4x.LIB +-------------+ | MS-DOS | MS-DOS Operating System +-------------+ 0000:0000 ------->
Figure 2 (above): The BRUN4x.EXE Run-Time Module Memory for an .EXE
This third figure (below) shows the run-time memory map when the stand- alone library (BCOM4x.LIB) option is used with the separate compilation (Make EXE File...) method:
+-------------+ |Communication| User specified size | buffers | +-------------+ | | This area contains less frequently | | used items, such as large | FAR heap | numeric arrays, the user | | environment table, dynamic | | arrays. | | +-------------+ User Data ------->|Run-time heap| Files, buffers, etc. End DS:xxxx +-------------+ | String heap | +-------------+ | User Stack | Preset to 2K bytes +-------------+ | Named | Named COMMON areas | COMMON | DGROUP +-------------+ | BC_DATA | User program variables Up to +-------------+ 64K | BC_CONST | User program constants +-------------+ | Blank COMMON| Library and user definitions +-------------+ | _DATA | QuickBasic run-time data areas, User Data | CONST | used during user code execution Start DS:0 ------->| _BSS | +-------------+ |Run-time code| Run-time code linked into file +-------------+ | User Code | User program separately linked Low Memory ------->| | with BCOM4x.LIB +-------------+ | MS-DOS | MS-DOS Operating System +-------------+ 0000:0000 ------->
Figure 3 (above): The Stand-Alone (BCOM4x.LIB Library) Memory for an .EXE
Additional query words: QuickBas 4.00 4.00b 4.50 6.00 6.00b 7.00 7.10 1.00