Skip to content
Techniques & Technology

Variables and Memory

BASIC memory management

How variables work internally and memory management for BASIC programs

commodore-64 referencevariablesmemorybasic

Variables and Memory

Variables are boxes with labels—type SCORE=100 and the C64 stores 100 somewhere in memory with the label "SCORE". But where? How much space does it use? What happens when you create arrays or long strings?

Understanding variable storage isn't just academic—it affects program size, speed, and whether your game crashes when it runs out of memory.

This article explains how BASIC manages variables, where they live in memory, and practical strategies for efficient memory use in games.

Memory Layout Overview

When you turn on the C64, BASIC carves up memory into distinct regions:

Address RangeSizePurpose
$0000-$00FF256 bytesZero page (fast access)
$0100-$01FF256 bytesStack
$0200-$03FF512 bytesBASIC input buffer, variables
$0400-$07E71000 bytesScreen RAM
$0800-$9FFF~38KBBASIC program and variables
$A000-$BFFF8KBBASIC ROM
$C000-$CFFF4KB(free, or for machine language)
$D000-$DFFF4KBI/O registers (VIC-II, SID, CIA)
$E000-$FFFF8KBKERNAL ROM

Key insight: Your BASIC program and all its variables share the $0800-$9FFF region—approximately 38KB total.

The BASIC Program Area

When you type a BASIC program, it's stored starting at $0801 (2049 decimal). Each line is tokenized:

Line format in memory:
[Next line pointer][Line number][Tokenized statement][End marker]

Example:

10 PRINT "HELLO"

Stored as:

  • 2 bytes: Pointer to next line
  • 2 bytes: Line number (10)
  • 1 byte: PRINT token (153)
  • 7 bytes: " "HELLO" " (includes quotes and space)
  • 1 byte: End-of-line marker (0)

Total: 13 bytes for this line.

Where Variables Live

Variables are stored after the BASIC program, growing upward in memory. Arrays and strings are stored above simple variables.

Memory layout (growing upward):
┌────────────────────────────┐ $9FFF (top of BASIC RAM)
│  Free space                │
├────────────────────────────┤
│  String storage (grows ↓)  │
├────────────────────────────┤
│  Arrays                    │
├────────────────────────────┤
│  Simple variables          │
├────────────────────────────┤
│  BASIC program             │
└────────────────────────────┘ $0801 (start of BASIC)

Critical points:

  • Program stored at bottom
  • Variables immediately after program
  • Arrays above simple variables
  • Strings allocated from top, growing downward
  • Free space shrinks as you add variables/strings

Run out of space → ?OUT OF MEMORY ERROR

Simple Variable Storage

Numeric Variables

Storage: 7 bytes each (2 bytes for name, 5 bytes for value)

a=10        rem 7 bytes
score=100   rem 7 bytes (name stored as "SC", remember!)
x=3.14159   rem 7 bytes

Format:

  • 2 bytes: Variable name (first two characters)
  • 5 bytes: Floating-point value

Integer variables (using % suffix):

a%=10       rem 7 bytes (not 2!)

Surprisingly, integer variables still use 7 bytes. The % just tells BASIC to truncate values to integers when assigned. There's no memory advantage.

Performance: Integer arithmetic is faster, but storage is identical.

String Variables

Variable record: 7 bytes (matching numeric records). String content is allocated separately in the string heap that grows downward from the top of BASIC memory.

The 7-byte variable record splits as:

BytesField
0-1Variable name (high bit of byte 1 set marks it as a string)
2String length (0-255)
3-4Pointer to string data in the heap
5-6Unused / padding
a$="hello"     rem 7-byte record + 5 bytes in string heap = 12 bytes total
name$="steve"  rem 7-byte record + 5 bytes in string heap = 12 bytes total
text$=""       rem 7-byte record + 0 bytes in heap        = 7 bytes total

Key difference: the string variable's record lives in the variable area with all other variables, but the content lives in the string heap up high. Heap fragmentation is what triggers garbage collection — see below.

Array Storage

Arrays consume significant memory. Plan carefully.

Numeric Arrays

Storage: 5 bytes overhead + (5 bytes × elements)

dim a(10)        rem 5 + (5 × 11) = 60 bytes
dim b(100)       rem 5 + (5 × 101) = 510 bytes
dim grid(20,20)  rem 5 + (5 × 21 × 21) = 2210 bytes

Formula:

Memory = 5 + (2 × dimensions) + (5 × total_elements)

Remember: DIM A(10) creates 11 elements (0-10 inclusive).

Integer Arrays

Storage: 5 bytes overhead + (2 bytes × elements)

dim a%(10)       rem 5 + (2 × 11) = 27 bytes
dim b%(100)      rem 5 + (2 × 101) = 207 bytes

Significant savings: Integer arrays use ~40% of the space of floating-point arrays.

When to use:

  • Tile maps (grid of tile types)
  • Entity IDs
  • Flags and states
  • Anything guaranteed to fit in -32768 to 32767

String Arrays

Storage: 5 bytes overhead + (3 bytes × elements) + actual string lengths

dim name$(10)    rem 5 + (3 × 11) = 38 bytes descriptors
                 rem + length of strings when assigned

Example:

dim name$(2)
name$(0)="bob"     rem adds 3 bytes
name$(1)="alice"   rem adds 5 bytes
name$(2)="eve"     rem adds 3 bytes
rem total: 38 + 3 + 5 + 3 = 49 bytes

String arrays are deceptive—descriptors take space even before you assign strings.

Memory Fragmentation and Garbage Collection

BASIC's string handling creates a messy problem: garbage.

The Garbage Problem

10 a$="hello"        rem allocates 5 bytes
20 a$="world"        rem allocates new 5 bytes, old string now garbage
30 a$="test"         rem allocates new 4 bytes, more garbage

Every time you reassign a string, the old string becomes garbage—still in memory but unreachable. Over time, this fragments memory.

Garbage Collection

When BASIC runs low on string space, it triggers garbage collection:

  1. Pause program
  2. Scan all string variables
  3. Copy active strings to fresh memory region
  4. Discard garbage
  5. Resume program

Symptoms:

  • Brief pause during gameplay
  • Noticeable on complex programs with heavy string manipulation
  • Can happen mid-frame, causing visible glitches

Games avoid this by:

  • Minimizing string operations
  • Preallocating strings and reusing them
  • Using numeric codes instead of strings where possible

String Concatenation Cost

a$=a$+"x"          rem EXPENSIVE

Every concatenation:

  1. Allocates new memory for result
  2. Copies old string
  3. Appends new content
  4. Abandons old string (now garbage)

Do this in a loop and you'll hit garbage collection constantly.

Better approach:

rem Pre-build strings once, reference them
100 dim msg$(5)
110 msg$(0)="get key"
120 msg$(1)="door locked"
130 msg$(2)="found gold"
rem ...use msg$(n) instead of building strings dynamically

The Variable Name Limitation

Most notorious BASIC V2 quirk: Only the first two characters of variable names matter.

score=100
screen=200
scroll=300
print score        rem prints 300!

All three variables are actually the same: SC.

Storage implication: When you create SCORE, BASIC stores "SC" as the name. Creating SCREEN doesn't create a new variable—it overwrites SCORE.

Strategy:

  • Plan variable names to have unique first two characters
  • Use single-letter names in tight memory situations
  • Keep a written list during development

Example naming scheme:

sc    rem score
pl    rem player lives
en    rem enemy count
xp    rem x position player
xe    rem x position enemy

Checking Memory Usage

FRE() Function

print fre(0)       rem bytes of free memory

Returns: Bytes available before OUT OF MEMORY error.

Important: Calling FRE(0) triggers garbage collection, so value may jump if garbage existed.

Use during development:

100 print "free:";fre(0)
110 dim map%(39,24)
120 print "after map:";fre(0)

Track how much memory each major structure consumes.

Protecting Memory Regions

Games often need to reserve memory for machine language routines, character sets, or sprite data.

Lowering BASIC's Top

The pointer to "top of BASIC RAM" is at $37/$38 (decimal 55-56). Setting it to a lower address makes BASIC stop using memory beyond that point. The high byte at $38 is the most significant — that's the byte you change to set the boundary in 256-byte units.

poke 56,48:poke 55,0:clr    rem set top of BASIC to $3000 (12288)

What this does:

  • POKE 56,48: high byte of "top of BASIC" = $30. Combined with the low byte = $00, top is $3000.
  • POKE 55,0: low byte = 0 (so the boundary lands on a 256-byte page).
  • CLR: clear variables and arrays — required, because the array pointers will point above the new boundary.

Effect: BASIC now uses RAM from $0801 up to $3000. The region from $3000 to the next system boundary is yours.

Common boundaries:

The "free" column is what's actually accessible without additional bank-switching. With KERNAL banked out via $01 you can also reclaim the $E000-$FFFF region (8 KB) for RAM, but that's a separate operation.

Top of BASICPOKE valuesFree RAMNotes
$C000 (49152)POKE 56,192:POKE 55,0:CLR$C000-$CFFF = 4 KB$D000+ is I/O; $E000+ is KERNAL
$A000 (40960)POKE 56,160:POKE 55,0:CLR$A000-$BFFF + $C000-$CFFF = 8 KBOnly if BASIC ROM is banked out at $A000
$8000 (32768)POKE 56,128:POKE 55,0:CLR$8000-$9FFF + $A000-$CFFF = 12 KBSame caveat about banking
$3000 (12288)POKE 56,48:POKE 55,0:CLR$3000-$9FFF = 28 KBCommon for ML game engines that load high

Warning: Must do this before defining arrays or variables. CLR wipes everything.

To unlock more RAM, bank ROMs out: POKE 1,53 clears HIRAM (KERNAL → RAM at $E000-$FFFF) while keeping I/O visible. Combined with a lowered BASIC top, this can free 16 KB+ contiguous, but you lose KERNAL routines (PRINT, GET, file I/O) until you re-enable HIRAM.

Practical Memory Management

Strategies for Large Programs

1. Use integer arrays wherever possible

dim map%(39,24)    rem 2 bytes per tile, not 5

2. Avoid dynamic strings

rem BAD - creates garbage
10 msg$="score: "+str$(sc)

rem BETTER - direct output
10 print "score:";sc

3. Preallocate arrays once

100 dim enemy_x(19), enemy_y(19), enemy_type(19)
110 rem use these arrays throughout game

4. Recycle variables

rem Instead of:
a=peek(1024):b=peek(1025):c=peek(1026)

rem Reuse one variable:
t=peek(1024):x=t
t=peek(1025):y=t
t=peek(1026):z=t

5. Abbreviate keywords

rem Full version (easier to read):
10 print "hello"

rem Abbreviated (saves bytes):
10 ?("hello"

BASIC stores abbreviated keywords as single-byte tokens, saving program space.

When Memory Runs Low

Symptoms:

  • ?OUT OF MEMORY ERROR
  • Unexpected crashes during array DIM
  • String operations fail

Solutions:

  1. Remove REM statements (they consume program space)
  2. Shorten line numbers (1,2,3 vs 1000,1010,1020)
  3. Combine statements (use : to put multiple on one line)
  4. Use single-letter variables (saves tokenized program size slightly)
  5. Reduce array sizes (do you really need 100 elements?)
  6. Move to machine language (1KB of ML can replace 5KB of BASIC)

Variables in Machine Language

When you call machine language routines from BASIC, you need to pass variables. BASIC stores them in specific places:

Passing Numeric Variables

Zero page usage:

  • Floating-point accumulator: $61-$66 (97-102)
  • Integer accumulator: $14-$15 (20-21)

Example:

10 a%=100
20 sys 49152       rem ML routine at $C000
30 print a%        rem ML can modify a%

The ML routine reads/writes $14-$15 to access A%.

Passing String Variables

Strings are trickier. Use PEEK() to get string descriptor, then read string data:

100 a$="test"
110 la=peek(peek(45)+peek(46)*256)        rem length
120 lo=peek(peek(45)+peek(46)*256+1)      rem address low
130 hi=peek(peek(45)+peek(46)*256+2)      rem address high
140 rem string data at hi*256+lo, length la

Reality: Most games avoid passing strings to ML. Use numeric codes instead.

Memory Map for Game Programmers

Typical game memory layout:

$0000-$00FF   Zero page (BASIC/KERNAL use, some free bytes)
$0100-$01FF   Stack
$0200-$03FF   BASIC variables (some free)
$0400-$07E7   Screen RAM
$0800-$0FFF   BASIC program (~2KB)
$1000-$1FFF   Game data arrays (~4KB)
$2000-$3FFF   Character set / sprites
$4000-$7FFF   Machine language game engine
$8000-$9FFF   Music/sound effects
$A000-$BFFF   BASIC ROM (or banked RAM)
$C000-$CFFF   Sprite data / extra storage
$D000-$DFFF   I/O (VIC-II, SID, CIA)
$E000-$FFFF   KERNAL ROM

Strategy:

  • Small BASIC program as loader/menu
  • Arrays for level data
  • Machine language for game loop
  • Graphics/music in protected high memory

See Also


Memory is your most constrained resource. Treat it like treasure—spend wisely, recycle aggressively, and never waste a byte.