Skip to content

Variables, DIM & memory

This page is about variables, the named places where a program keeps its values. It explains what DIM does, the difference between declaring a name and setting aside memory for it, which parts of a program can see a variable and for how long, and how to give memory back when you are done. After reading it you will be able to declare every kind of value d/BASIC has and keep a program's memory use deliberate.

A variable is a name with three promises: a type, a place to live, and a lifetime. DIM makes those promises visible. That matters on every machine, and it matters most when the same program may run with megabytes of memory on a modern computer and with kilobytes on an 8-bit one.

Start with the ordinary form

DIM Score AS INTEGER
DIM Distance#
DIM CustomerName AS STRING
DIM Price AS DECIMAL

Score = 12
Distance# = 41.75
CustomerName = "MIRA"
Price = 19.9500@

You may put compatible declarations on one line:

DIM Left AS INTEGER, Top AS INTEGER
DIM Width%, Height%

A type suffix such as # or % is part of the declaration. An AS clause is usually easier to read in code that other people will call; suffixes remain excellent for short local calculations.

DIM always creates a binding: a name the program can use. Whether it also sets aside storage depends on the type and shape:

Declaration Initial meaning
DIM Count AS LONG one numeric value, initially 0
DIM Label AS STRING one variable-length string, initially empty
DIM Point AS PointType one TYPE record stored in place, with its fields initialized
DIM Button AS WorksButton one reference to an object, initially NOTHING (the empty reference)
DIM Samples%(0 TO 255) one array binding plus 256 initialized elements
DIM Samples%() an array name with no shape yet (a null binding)

WorksButton in these examples is a button from the widget library of d/Works, the office suite being written in d/BASIC (d/Write, d/Calc, d/Present); see d/Works Widgets.

This is the distinction that prevents a great deal of confusion:

DIM declares a reference to an object. NEW builds the object it may refer to.

DIM Save AS WorksButton

IF Save IS NOTHING THEN PRINT "NOT CONSTRUCTED"
Save = NEW WorksButton("save", "Save")

So a constructor runs because of NEW, not because a class name appeared after AS. Assigning NOTHING lets go of the object that variable was holding.

Explicit declarations beat accidental storage

d/BASIC accepts compact listings in which a variable first appears when it is assigned. Its type then follows its suffix, a DEF* range, or the default numeric rule. That is useful for code brought over from an old machine, but a substantial new program should declare any value that lives longer than a few lines.

DEFINT I-N

DIM ItemCount AS INTEGER
DIM RunningTotal AS LONG
DIM ReportTitle AS STRING

Otherwise an undeclared numeric name is SINGLE. DIM Count is not shorthand for DIM Count AS INTEGER.

Scope answers “who can see it?”

Form Visible from Lifetime
top-level DIM top-level module code program or application session
top-level DIM SHARED module code and its procedures program or application session
procedure DIM that one call until the call returns
procedure STATIC that procedure kept between calls
class instance field methods and allowed users of that object until its owning object is destroyed
class STATIC field the class-qualified name program or application session
DIM SHARED RunCount AS LONG

SUB Visit ()
  STATIC Previous AS LONG
  RunCount = RunCount + 1
  PRINT "NOW"; RunCount; " PREVIOUS"; Previous
  Previous = RunCount
END SUB

Keep state in the narrowest scope that works. A local is easier to reason about than a shared value; a field inside an object is easier to combine with other code than an unrelated global.

Arrays allocate a shape

Bounds are inclusive:

DIM Week%(1 TO 7)
DIM Board%(0 TO 7, 0 TO 7)
DIM Samples#()

REDIM Samples#(0 TO 255)

The empty parentheses in DIM Samples#() reserve only the name. REDIM later creates or replaces its shape. REDIM PRESERVE keeps the values that fall inside both the old and the new bounds:

REDIM PRESERVE Samples#(0 TO 511)

If a resize cannot be completed, because memory ran out or a value could not be converted, the operation is declined and the program is told why, and the array keeps its previous shape and contents. d/BASIC never leaves you with a half-resized array. ERASE releases the whole thing and returns the name to its empty state:

ERASE Samples#

Arrays covers dimensions, bounds, ARRAYCOPY, ARRAYFILL, and ARRAYSWAP, which exchanges two arrays without copying, in detail.

Records live inline; objects have identity

TYPE Coordinate
  X AS INTEGER
  Y AS INTEGER
END TYPE

DIM Here AS Coordinate
DIM There AS Coordinate

Here.X = 12
There = Here             ' independent record copy

A TYPE value is its fields. Assigning it makes a copy. A CLASS value is a reference to an object that has its own identity, and d/OS keeps count of how many references point at each object. Assigning it makes another reference to that same object:

DIM First AS WorksButton
DIM AlsoFirst AS WorksButton

First = NEW WorksButton("go", "Go")
AlsoFirst = First
PRINT First IS AlsoFirst        ' -1

That value/reference distinction also decides what a procedure can change when you pass it an argument; see BYREF and BYVAL.

Strings are managed values

A STRING variable is not a fixed array of characters and gives you no pointer to its bytes. Assignment, joining, slicing, and replacement all produce string values that the language manages for you, and the d/OS runtime, the program that runs your compiled code, frees text that nothing refers to any more at a predictable point rather than at some unknown later time.

DIM Given AS STRING
DIM Family AS STRING
DIM DisplayName AS STRING

Given = "Ada"
Family = "Lovelace"
DisplayName = Given + " " + Family

Read Strings & Text before slicing text that a person typed in UTF-8: string positions count bytes, not characters, while the text-entry parts of the user interface hand your program whole typed text (and text still being composed) as events.

Running out of memory is handled, not hidden

A d/BASIC program never sees a memory address, never holds a token from an allocator, and cannot do pointer arithmetic. Instead, the compiled program file, .dbc, states up front how much memory it needs, both fixed and while running, and each d/OS runtime only accepts a program it can actually serve. Work done while running can still outgrow what is available: a large REDIM, a long string join, a big web of objects, or a decoded image or sound may be declined rather than allowed to overwrite another value.

On the first 6502 machines (the 6502 is the processor in the Atari 800, the Commodore 64 and the Apple II), a program starts with a 512 KiB pool of program memory. The 6502 large-memory chapter explains how that memory is banked and paged behind the scenes, how the memory tiers work, and how intents (an intent is a named request such as "fill this rectangle" or "play this tone" that the machine carries out however it can) are sped up.

Practical habits:

  1. Give arrays the bounds the algorithm actually needs, no more.
  2. Reuse long-lived buffers when that makes it clearer who owns them.
  3. Use ARRAYSWAP to exchange front and back buffers instead of copying large arrays.
  4. Release arrays with ERASE, objects with NOTHING, and host sessions with their explicit close operation.
  5. Make sure cleanup runs whether the work succeeded or was declined.

A complete storage example

DEFINT A-Z

DIM SHARED Visits AS LONG

SUB Summarize (Values() AS INTEGER)
  DIM Total AS LONG
  FOR I = LBOUND(Values) TO UBOUND(Values)
    Total = Total + Values(I)
  NEXT I
  Visits = Visits + 1
  PRINT "TOTAL"; Total; " VISIT"; Visits
END SUB

DIM Readings(1 TO 4) AS INTEGER
ARRAYFILL Readings(), 5
CALL Summarize(Readings())
ERASE Readings
END

The example has a value that lives as long as the program, a value that lives for one call, one explicit array allocation, one statement that changes a whole array, and one explicit release. No memory address is part of the program's meaning.


Next: Strings & Text →