THE BEGINNER'S PAGE
Richard Mansfield, Senior Editor
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
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 120 NEXTA 130 SYS864 : FORB = 1TO5 140 IFB = 1THEN160 150 NEXTB 160 SYS864 : FORC = 1TO5 170 IFC = 1THEN190 180 NEXTC 190 SYS864 : FORD = 1TO5 200 IFD = 1THEN220 210 NEXTD 220 SYS864 : FORE = 1TO5 230 IFE = 1THEN250 240 NEXTE 250 SYS864 : FORF = 1TO5 260 IFF = 1THEN280 270 NEXTF 280 SYS864 : FORG = 1TO5 290 IFG = 1THEN310 300 NEXTG 310 SYS864 : FORH = 1TO5 320 IFH = 1THEN340 330 NEXTH 340 SYS864 : FORI = 1TO5 350 IFI = 1THEN370 360 NEXTI 370 SYS864 : FORJ = 1TO5 380 IFJ = 1THEN400 390 NEXTJ