Classic Computer Magazine Archive COMPUTE! ISSUE 71 / APRIL 1986 / PAGE 68

Loading And Linking Commodore Programs
Part 2

Jim Butterfield, Associate Editor

Are you running out of memory for your programs? You don't necessarily have to buy a bigger computer. This month's installment shows how a technique called chaining lets you break up a large program into smaller parts to work on a common task. The technique applies to all Commodore computers, with either disk or tape.


There are three major ways of connecting programs together. Chaining allows several. programs to perform a job, each program continuing the work that a previous program has started. Load linking lets one program load another program, with the new program starting fresh on a new task. Overlaying lets a main program call in additional subroutines, data tables, or graphics information. This month we'll discuss chaining.
    When one of a series of programs has completed its share of the work, it may chain to a following program to continue processing the data. In effect, several programs group together to create a bigger program. On Commodore computers, chaining works with disk or tape. It's more common with disk because the various programs can be brought in more quickly. If used with tape, you can arrange the programs sequentially on the cassette so little time is lost in searching for the next program. We'll use disk for the following examples, but they can be readily converted to tape.

Why Chaining?
The most obvious reason to chain programs is to save memory space. On small computers, there isn't enough room for big jobs. So the program is broken up into "chunks." Each chunk is small enough to fit into memory, each does a specific task, and together they do the whole job. Even on computers that seem to have lots of memory, you may need to resort to chaining to relieve congestion. For instance, even though the Commodore 64 begins with 38,911 bytes of free memory, arrays of data can quickly fill up much of this work area.
    Sometimes program flow is an important reason for chaining. If a statistical program has been processing some data, it might ask the user to choose from several options (draw a graph, print the data, etc.). Depending on which option is chosen, it may be convenient to call in a selected program to do the next job. In this way, the original program needn't be cluttered with code to cover all the possible options; instead, the options are handled by programs called in as required.
    Likewise, it's possible to write a program that starts up in several different ways. In one case, it might collect the data it needs from DATA statements. Another time, it might require input from the keyboard. On still other occasions, it might compute the data, read it from a file, or detect it by external sensors. No matter. We'll start up whatever "acquisition" program is appropriate, and when the data is ready to be processed, the computer can chain to a common processing program.
    Chaining is also a worthwhile exercise which can force you to break your programs into well thought-out modules. Your program can't leap about at will, since it can only reach whatever is in the current module; and you must tie up loose ends before you go to the next unit. Each time you chain, FOR-NEXT loops are scrapped, subroutine RETURNs are canceled, and the DATA pointer is RESTOREd. You must make sure that these program areas are tidy before you chain, since they will otherwise be lost.

Program Architecture
A major advantage of chaining is that you don't lose variables between programs. Values, strings, and arrays that have been worked out by a previous program are carried through to the next program segment. This is useful, but it also calls for careful handling-we don't want to mash these values inadvertently.
    Figure 1 shows how programs, variables, and arrays lie in memory. The point marked start-of-BASIC is where the program starts in memory. Behind the program is a point called start-of-variables; beyond this point the computer stores variables and arrays.
    You usually don't need to know the exact addresses of these memory points; the computer takes care of the housekeeping for you. String variables go into this area, too-although not the strings themselves, just three-byte descriptors that say where the strings are located and how long they are. (More on this later.)
    Suppose you have a large program that chains to a smaller program. Figure 2 shows this happening.
    The variables don't move; behind the second program is wasted space that isn't used. This creates no problem when you run the program. However, after this kind of chaining has taken place, you should not SAVE the second program or you'll save the wasted area too (SAVE always stores from the start-of-BASIC point to just before start-of-variables).
    Here comes the problem. Let's take the reverse situation: a small program that chains to a larger one. Figure 3 shows the difficulty that results.
    The big program overwrites and destroys the variables created by the first, smaller program. To keep this from happening, our first program must be the biggest of the two, or at least the same size.
    If several programs are chained together, this rule always applies. The first program must be as big or bigger than any other program. It sets the start-of-variables point, and it must set it high enough so that all following programs won't run into trouble (for more information on this point, see "Commodore Program Chaining," COMPUTE!, December 1985). The Commodore 128 in 128 mode doesn't need to worry about this problem. Since it keeps variables in a separate memory bank, loading a new BASIC program can't harm them.

figures 1,2,3

Strings And Descriptors
As noted earlier, the variable and array area holds string information (the descriptors), but not the strings themselves. There are two places where the actual strings might be, and it's important to know about them. Say that your program contains a line like this:
370 A$="GORILLA"

    When this line executes, the computer makes an entry in the variable table showing that there is now a variable called A$, that its length is seven characters, and that it is located at its present position in the program text itself. Except on the 128, the string is used from where it lies within the program. The computer decides that there's no point in making an extra copy of GORILLA; when it needs this string, it takes it from the BASIC program line. This type of string is called static because it never moves from its original location. Static strings can mean trouble if you chain programs: Since chaining replaces the original program text with a second program, all static strings-which exist only in the first program's text-are destroyed.
    There's a second kind of string, and that's the one we must use here. If a program contains a statement like INPUT A$, the string which is typed by the user must be stored somewhere. This is called a dynamic string; the computer stores it in a safe place where it won't be disturbed by chaining.
    Dynamic strings are created in two ways: by INPUT or GET statements and by string manipulations (LEFT$, RIGHT$, STR$, concatenation, and so on). It's simple to change a static string into a dynamic one. The statement A$="GORILLA" + "" concatenates (adds together) the strings "GORILLA" and "". Since "" is a null (empty) string, this statement really means "add nothing to the string GORILLA." Though the contents of the string don't change, the computer is convinced that we now have a new string which must be stored elsewhere in memory.
    Again, the Commodore 128 in 128 mode doesn't need to worry about this problem. Strings are kept in a separate memory bank, and there's no such thing as a static string in 128 mode.

Chaining Rules
Let's summarize the rules for wellchained programs:

    • The first program in the chain must be as big or bigger than all subsequent programs.
    • Any strings you need to pass from program to program must be dynamic, not static.
    • If you use DEF FN definitions, redefine them in each program.
    • Arrays should be DIMensioned only once, preferably in the first program.

A Short Example
Let's write a small series of programs to demonstrate how this works. Our first program is called MAIN:

100 IF N>0 GOTO 200

    The variable N can only be zero when we start, so we won't jump ahead. But if we ever chain back to this program, we'll take the branch to line 200.

110 PRINT "SIMPLE GRADEBOOK DE
   MO"
120 DIM N$(15),M(15)
130 N=8

    For simplicity, we'll assume eight students. When the program runs, you can invent their names and numeric grades.

140 FOR J=1 TO N
150 PRINT "STUDENT";J;
160 INPUT "NAME";N$(J)
170 INPUT "GRADE";M(J)
180 NEXT J

    Running the program at this point gives you data on eight students. If you ever chain back to this original program, it will branch to line 200 (remember the IF test in line 100).

200 PRINT
210 PRINT "DO YOU WANT TO--"
220 PRINT "1. CALCULATE AVERAG
   E"
230 PRINT "2. CALCULATE HIGH/L
   OW SCORES"
240 PRINT "3. QUIT"
250 PRINT
260 INPUT "YOUR CHOICE (1-3)";
   C
270 ON C GOTO 300,310,320
280 GOTO 260
300 LOAD "C.AVG",8
310 LOAD "C.HIL",8
320 END

    Note that line 300 will not run into line 310, nor 310 into 320. The moment you perform LOAD within a program, the new program loads and runs immediately. Type this program and then save it as MAIN (don't save it under any other filename). Now type NEW and enter program C.AVG as follows:

100 PRINT
110 A=0
120 FOR J=1 TO N
130 A=A+M(J)
140 NEXT J
150 PRINT "AVERAGE SCORE,";N;"
   STUDENTS=";A/N
160 PRINT
170 LOAD "MAIN",8

    That's it. Check it closely and save it as C.AVG (again, the filename is important; don't change it). Now type NEW and enter program C.HIL as follows:

100 PRINT
110 H=M(1):L=M(1)
120 FOR J=1 TO N
130 IF H<M(J) THEN H=M(J)
140 IF L>M(J) THEN L=M(J)
150 NEXT J
160 PRINT "HIGH SCORE:";H;" BY
    ..."
170 FOR J=1 TO N
180 IF H=M(J) THEN PRINT N$(J)
190 NEXT J
200 PRINT "LOW SCORE:";L;" BY
    ..."
210 FOR J=1 TO N
220 IF L=M(J) THEN PRINT N$(J)
230 NEXT J
240 PRINT
250 LOAD "MAIN",8

    Again, check your typing closely and save the program as C.HIL to complete the set. Now load program MAIN and you're ready to try out chaining. Note that MAIN is definitely larger than the other two. If there's any doubt in your mind, add some extra REM statements to MAIN to make it bigger.

Side Effects
We mentioned earlier that the act of chaining causes certain things to happen. FOR-NEXT loops are scrapped, subroutine RETURNs are canceled, and the DATA pointer is RESTOREd. That makes sense: You can't RETURN to a program that has disappeared, for example. And occasionally, these side effects can be useful. For instance, can a program ever chain to itself? The answer is yes, but at first it's hard to see why you'd want to do so. What's the point of loading a program that's already there? The answer lies in these side effects.
    Sometimes a program gets stuck deep in a subroutine and can't find its way out. With good programming, this should never happen. All subroutines should RETURN neatly, and if there's an error or similar anomaly, the information should be logged into a flag and detected at the appropriate program level. It's easy to give that sort of advice-but sometimes a program is deep within several nested levels of subroutines when the user commands, "Forget all this and take me back to the menu." Sensible programmers know that you can't jump directly out of these subroutines back to the main menu, and it's a long, long trail to backtrack the whole way.
    In case of emergency, you can chain the program to itself. As it loads itself back in, it shakes off all the FOR-NEXT loops and subroutine levels and surfaces cleanly - with all variables in place-at the first statement. Just to show it can be done, we'll write a dreadful program that does just that. Please don't write programs this way: It's here just to illustrate a point. Remember to type NEW before entering this program.

100 IF N>0 GOTO 130
110 PRINT "NAME LIST"
120 DIM N$(50)
130 PRINT
140 PRINT "DO YOU WANT TO --"
150 PRINT " 1. ENTER NAMES"
160 PRINT " 2. LIST NAMES"
170 PRINT " 3. QUIT"
180 INPUT "YOUR CHOICE";C
190 ON C GOSUB 210,310,350
200 GOTO 130
210 PRINT "ENTER EACH NAME"
220 PRINT "FOLLOWED BY AN '*'
    CHARACTER"
230 PRINT "TO END ENTRY"
240 GOSUB 260
250 GOTO 240
260 INPUT N$
270 IF N$="*" OR N=50 THEN LOA
   D "DEMO",B
280 N=N+1
290 N$(N)=N$
300 RETURN
310 FOR J=1 TO N
320 PRINT N$(J)
330 NEXT J
340 RETURN
350 END

    Check the program and save it with the filename DEMO; be sure to use that filename, since the program uses it to load itself.
    DEMO is a program turned bad, and you should try not to get yourself into a similar problem. By the time this program reaches line 210, it's in a subroutine; at line 260, it's nested within a second subroutine. When line 270 discovers that the user wants to exit, the poor programmer doesn't know how to get out. GOTO 130 would be a very bad solution: Jumping out of the routine with GOTO instead of RETURN leaves unprocessed subroutine information on the computer's stack (which can eventually cause an OUT OF MEMORY error). What to do?
    The second-best solution (shown here) is to clean up the program with a chain to itself. The best solution is not to get yourself into this kind of mess in the first place.
    Chaining can be a useful and powerful technique. There are some rules to remember-especially that of making sure the first program is the biggest-but in general it works quite well. Don't confuse chaining with loading, where one program loads and starts another. In this case, there's no passing of variables; the new program starts clean. We'll talk about loading in next month's installment.