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:
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.
Declaration and allocation are related, not identical¶
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:
DIMdeclares a reference to an object.NEWbuilds 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.
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:
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:
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:
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:
- Give arrays the bounds the algorithm actually needs, no more.
- Reuse long-lived buffers when that makes it clearer who owns them.
- Use
ARRAYSWAPto exchange front and back buffers instead of copying large arrays. - Release arrays with
ERASE, objects withNOTHING, and host sessions with their explicit close operation. - 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 →