Classic Computer Magazine Archive COMPUTE! ISSUE 46 / MARCH 1984 / PAGE 126


Richard Mansfield, Senior Editor

Computer Amnesia

Here's a nasty little problem that can completely baffle you if you don't know what's causing it. We'll provide a short utility program that will cure this deadly error—but first, let's explore the symptoms. It appears in several disguises.

As you begin to write longer and more complicated programs, sooner or later your computer will halt execution and announce that you're OUT OF MEMORY. You know you're not. When you ask for a report of free memory (? FRE, or whatever command your BASIC uses), there's a lot of room left. But the computer is claiming that it has no more memory left. What's going on?

Something's Gone Awry

Try Program 1. After you type RUN, the computer will obediently follow your instructions and then grind to a halt. Your machine won't smolder, but something's gone awry. Clearly, these three lines cannot be using up all the memory in even the smallest computer.

Notice that there is no RETURN instruction to match the GOSUB. We are continuously GOSUBing, but always jumping back without a proper RETURN. That's variation one of this problem. Whenever the computer comes upon a GOSUB, it makes a mental note of where it is currently located so it can RETURN there. In Program 1, the computer would make a note that "line 150" was the correct place to RETURN. These mental notes are put on a stack, a zone in memory from addresses 256–511 (in 6502-based computers). As each note gets put on the stack, it takes up more room in the stack.

When the computer comes upon a RETURN instruction, it pulls off the most recent note and knows where to jump back to. Program 1, however, has no RETURN and so those notes keep piling up in the stack. Pretty soon, the computer is out of stack memory because each GOSUB puts a two-byte-sized note on the stack. To make things worse, some versions of BASIC use part of stack memory for their own purposes, making the stack smaller still.

A Common Stack Staffer

In a cleanly written program, you'll always RETURN from every GOSUB. When you're writing large programs, however, that's easier said than done. It's hard to keep track of everything. Added to that, there's an even more subtle way to run out of stack space: early exit loops.

Look at Program 3. It's a very common technique to set up a loop and then test something, exiting the loop if the test succeeds. In such cases, you keep bouncing between FOR and NEXT until the IF part is satisfied. (For the moment, don't pay any attention to lines 10–20 and the SYS statement.) When, in line 110, A = 1, we jump out of that FOR/NEXT loop and into another one. And we start searching for B. The first loop was never completed. That is, we left an unsatisfied NEXT A because it didn't get to count up to 5 as it wanted to. It wouldn't make much difference if these NEXTs were unsatisfied except that this condition, too, leaves something on the stack. This isn't quite the stack stuffer that unRETURNed GOSUBs are, but it does eventually cause an overflow and an OUT OF MEMORY.

Solving The Problem

So, if you run into this mysterious memory loss, check through your program first for early exits from GOSUBs (that's the most likely cause). Then, if that's not it, look at your FOR/NEXT loops. The cure for GOSUBs is to create a RETURN to satisfy each one. The cure for the loops is to use the same variable name again. In Program 3, if we write IF A, IF A, IF A, instead of IF A, IF B, IF C, there would be no problem. Reusing an IF variable will clean the stack for you.

Experienced programmers make it a habit to use I for almost every FOR/NEXT loop, J if they need a loop within the I loop, and T for timing loops. That way, they keep the stack clean without having to think about it.

Lines 10–20 in Programs 2 and 3 are a short utility that can be attached to any program and give a report of the memory left within the stack. As written, the DATA line contains the information for the Commodore 64 version of this utility, "Stackwatch." Replacements for this line to make it work on other computers are given below.

If you've been working on a long, complicated program and are getting an odd out-of-memory error, add lines 10–20 to the long program. They'll stick a machine language program down in a safe place. Then, put SYS 864 into various places in your program. You can then quickly locate which loop or GOSUB is unclosed. When the number printed on screen by Stackwatch takes a big dip, hit the STOP key and see where you are.

To make Stackwatch work on other Commodore computers and the Apple, you must change the last three items in the DATA line, line 10, as follows:

for Original ROM PET: 159,220,96
    Upgrade ROM PET:  217,220,96
    4.0 BASIC PET:    131,207,96
    VIC-20:           205,221,96
    64:               (as printed)
    For Apple:        10 DATA 186,169,0,32,36,207,96

There is no comparable number printing routine within the Atari operating system, but Charles Brannon has provided the following replica for those who know machine language and want to implement Stackwatch on the Atari.

For TI, you can run these BASIC tests, but the TI's brain chip will not run Stackwatch.

0000          0100           *=   $600
0600          0110           .OPT OBJ
              0120    ;Test routine
0600  A900    0130           LDA  #0
0602  A264    0140           LDX  #100
0604  200806  0150           JSR  PRNUM
0607  60      0160           RTS
              0170    ;
              0180    ; MSB in A, LSB in X
              0190    ; Prints number to screen
              0200    ;
0608  86D4    0210    PRNUM  STX $D4
060A  85D5    0220           STA $D5
060C 20AAD9   0230           JSR $D9AA
060F 20E6D8   0240           JSR $D8E6
              0250    ;
              0260    ; Print ASCII number pointed to
              0270    ; by $F3 and $F4
              0280    ; The last digit of the number will be signaled by bit 7
              0285    ; If it is set, then we have the last digit
0612 A000     0290           LDY #0
0614 84CB     0300 LOOP      STY $CB         ;Save Y index
0616 B1F3     0310           LDA ($F3), Y    ;Get char
0618 48       0320           PHA             ;save it on stack
0619 297F     0330           AND #$7F        ;mask off high bit (or it would be inverse)
061B 202706   0340           JSR PRCHAR      ;print character
061E 68       0350           PLA             ;restore character
061F 3005     0360           BMI EXIT        ;test for high bit set
0621 A4CB     0370           LDY $CB         ;restore Y index
0623 C8       0380           INY
0624 DOEE     0390           BNE LOOP
0626 60       0400 EXIT      RTS
              0410 ;
              0411 ;This routine pushes the high, low bytes of the address
              0412 ;of the CI0 print character routine onto the stack,
              0413 ;creating an artificial return address
              0414 ;In effect, we have an indirect jump
0627 AA       0415 PRCHAR    TAX
0628 AD4703   0420           LDA $0347
062B 48       0430           PHA
062C AD4603   0440           LDA $0346
062F 48       0450           PHA
0630 8A       0460           TXA
0631 A092     0470           LDY #$92
0633 60       0480           RTS
              0490 ;
0634          0500          .END

Program 1 : Memory Collapse

100 GOSUB 150
150 X = X + 1 : PRINT X
160 GOTO 100

Program 2:

Stackwatch Attached To Program 1

10 DATA 186, 169, 0, 32, 205, 189, 96
20 FOR A = 864TO870 : READ D : POKE A,D : NEXT A
100 GOSUB 150
150 X = X+1 : PRINTX
160 SYS864 : GOTO100

Program 3:Too Many Loops

10 DATA 186, 169, 0, 32, 205, 189, 96
20 FORA = 864TO870 :  READD :  POKEA,D :  NEXTA
100 FORA = 1TO5
110 IFA = 1THEN130
130 SYS864 :  FORB = 1TO5
140 IFB = 1THEN160
160 SYS864 :  FORC = 1TO5
170 IFC = 1THEN190
190 SYS864 :  FORD = 1TO5
200 IFD = 1THEN220
220 SYS864 :  FORE = 1TO5
230 IFE = 1THEN250
250 SYS864 : FORF = 1TO5
260 IFF = 1THEN280
280 SYS864 : FORG = 1TO5
290 IFG = 1THEN310
310 SYS864 : FORH = 1TO5
320 IFH = 1THEN340
340 SYS864 : FORI = 1TO5
350 IFI = 1THEN370
370 SYS864 : FORJ = 1TO5
380 IFJ = 1THEN400