Programmers may want to understand how Microsoft QuickBasic and MicrosoftBasic Compiler arrange memory in order to write programs that makeefficient use of system resources. The QuickBasic language provides avariety of data types and modular code constructs that allow you to managethe 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 thisarticle.
Appendix B explains the .MAP file created when the Appendix A programexample is linked. You can use the LINK map to determine how close eachmodule is to approaching the 64K code limit and how close the .EXEprogram's static variables are getting to the 64K limit in the DGROUP.
Appendixes C and D describe in detail the memory mapping for runningprograms in the following three different environments: the QB.EXE editor,a compiled .EXE program using the run-time module, and an .EXE compiledwith the stand-alone option.
A "module" is defined as an individual source file containing Basicprocedures.
"Module-level code" is defined as the statements within a module that areoutside a SUB...END SUB, FUNCTION...END FUNCTION, or DEF FN..END DEFdefinition block.
A "support module" is a source file, separate from the main module, thatcontains additional SUB or FUNCTION procedures.
Modular programming deals with splitting programs into separate modules(i.e., separate source files containing SUB or FUNCTION procedures). Thereasons 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 severalmodules. This is easily accomplished by breaking SUB or FUNCTION proceduresout of the main module and placing them in support modules. These modulesare then compiled separately with BC.EXE and LINKed with the main module asin 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 thefollowing 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 lineis used:
LINK Main.OBJ+Module2.OBJ+Module3.OBJ; ----> Main.EXE
Main.EXE is the finished executable program. When you compile inQuickBasic's environment (QB.EXE), the environment automatically compileseach module and LINKs them together to produce the same .OBJ files andfinished 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 followingoperations, 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 inmemory with the other module(s). To save all of the loaded modules, do thefollowing:
- Choose the File menu.
- Choose the Save All option.
When the modules are saved together, QuickBasic creates a file (with theextension .MAK) that keeps track of the main module and the other modulesthat are used by the main. To load all the modules in at once, do thefollowing:
- 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 and4.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 isLINKed with a main module to create an executable file. The module-levelcode of the main module is the only code directly executed by QuickBasic.The module-level code of the support modules cannot be CALLed, RUN, orCHAINed. 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 withinSUBprogram procedures [where ON Event means ON KEY(n), ON COM(n), ONTIMER(n), etc.]. However, the line or line label that is the target of theevent or error trap's GOTO or GOSUB must be at the module-level code in thesame module as that SUBprogram. This is because QuickBasic doesn't allowGOSUBs or GOTOs from a SUBprogram to other modules or SUBprograms, andQuickBasic allows only ON Event GOSUB and ON ERROR GOTO statements to jumpfrom 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 pastesextra 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 themodule-level code. Variables and arrays can be shared between modulesusing 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 variablesdeclared at the main module level of that module (without passing them asparameters). It does NOT give access to variables declared in supportmodules.
The COMMON statement makes variables available at the module level betweenmodules. The SHARED attribute for the COMMON statement (i.e., COMMONSHARED) is required to share the variables with SUBprograms or FUNCTIONs.The list of variables in the COMMON and COMMON SHARED statements must matchin type in the COMMON and COMMON SHARED statements in each module.
When using the COMMON (or COMMON SHARED) statement, static and dynamicarrays are dimensioned differently. Static arrays in COMMON must beDIMensioned BEFORE the COMMON statement in all the modules with that COMMONstatement. Dynamic arrays in COMMON must be DIMensioned AFTER the COMMONstatement in just the main module and should not be DIMensioned in anysupport modules.
There are two differences between using the SHARED statement and the DIMSHARED 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 allarrays 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 morememory in DGROUP by making the arrays dynamic, which moves them into thefar heap. To make an array dynamic, DIMension the array after themetacommand REM $DYNAMIC, or DIMension the array with a variable in itssubscript.
All dynamic arrays are stored on the far heap except arrays of variable-length strings. Strings, variable-length string arrays, and simplevariables are always stored in DGROUP. To store strings in the far heap,you must DIMension a dynamic array of fixed-length strings. DIMensioningstrings as fixed length in dynamic arrays helps to free up storage spacethat 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 avariable 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 preferablybe a power of 2, in order to avoid wasted memory and to enable arrays to belarger than 128K, as explained below.
Space is allocated for a huge array contiguously in the far heap, with therestriction that no single array element (or record) is allowed to be splitacross a 64K boundary. If a record size is not a power of 2, the array isallocated at an offset high enough, relative to the array's base segmentaddress (returned by the VARSEG function), such that no array element issplit across the boundary at exactly 64K above the base segment. The valuereturned by the VARPTR function for the first element of the array thenindicates both the offset of the array, and also the size of a gap createdin 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 theworst 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 largerthan 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 singlearray element is allowed to be split across a 64K boundary.
The final major aspect of QuickBasic memory management is that QuickBasicattempts to make efficient use of memory, which may cause variable-lengthstrings, variable-length string arrays, and dynamic arrays to move aroundin memory from statement to statement at run time. (Other variables have afixed location determined at EXE load time.) In addition, if you perform aCHAIN statement, then any data passed in a COMMON block may move. Thismeans that the results of the VARPTR, VARSEG, VARPTR$, or SADD functionshould always be used immediately after that function is invoked.
APPENDIX A: SAMPLE PROGRAM
This sample program demonstrates the topics covered in this article. Thisprogram gives an example of ways to DIMension arrays, and to pass variablesbetween modules and SUBprograms. The program consists of two modules, eachwhich 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 BytesEND TYPE ' 32 Bytes Total, a power of 2, 2^5REM $STATICDIM A(100) AS INTEGER ' Static array, stored in DGROUP.COMMON SHARED A() AS INTEGER ' Share the variables with the otherCOMMON SHARED B AS STRING * 20 ' module and its SUBprograms.COMMON SHARED X() AS INTEGERREM $DYNAMICDIM 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 $STATICDIM SHARED M(100) AS LONG ' These variables are shared with allDIM SHARED Equals AS STRING * 10 ' the SUBprograms in this module, ' and are stored in the DGROUP.Num = 40DIM 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) = 7A(1) = 4CALL Subdemo ' CALLs the SUBprogram of this module.CALL OtherModSub ' CALLs the SUBprogram of the support module.END'SUBprogram of the Main Module:SUB SubdemoSHARED 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 $STATICDIM A(100) AS INTEGER ' Only the static array is DIMensionedCOMMON SHARED A() AS INTEGER ' before the COMMON SHARED statement.COMMON SHARED B AS STRING * 20COMMON 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 PanicEND SUB
APPENDIX B: LINK .MAP FILE EXPLANATION
Below is a .MAP file created when the program example in Appendix A islinked. The .MAP file from a successful link shows how close a module'scode segment is approaching the 64K code limit, and how close the staticvariables for the entire .EXE program are to approaching the 64K limit inthe DGROUP. To generate a listing map of a program, include a map filenamein 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 FMGROUPProgram 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, and4.50 environment (QB.EXE). In the QB.EXE environment, an array that isstatic, not in COMMON, and not composed of variable-length strings, isstored in the far heap (instead of DGROUP, as in .EXE programs).
This changes memory management in your program, depending on where you runyour program.
Thus, in programs run within the QuickBasic 4.00, 4.00b, or 4.50environment (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 detailedrun-time memory maps.
General Memory Diagram
The following diagram summarizes how QuickBasic compiled programs areloaded 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 arrangementof code and data in memory at run time.
The first figure (below) shows the run-time memory map when the program isexecuted 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.EXELow 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 appearswhen the run-time module BRUN4x.EXE is used with the separatecompilation (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 variablesUp to +-------------+64K | BC_CONST | User program constants +-------------+ | Blank COMMON| +-------------+ | _DATA | QuickBasic run-time data areas,User Data | CONST | used during user code executionStart DS:0 ------->| _BSS | +-------------+ | User Code | User program separately linkedLow 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 variablesUp 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 executionStart DS:0 ------->| _BSS | +-------------+ |Run-time code| Run-time code linked into file +-------------+ | User Code | User program separately linkedLow 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