Reading/Writing Longs of Main Memory (Syntax 3)

In PUB and PRI blocks, syntax 3 of LONG is used to read or write long-sized values of main memory. This is done by writing expressions that refer to main memory using the form: long[BaseAddress][Offset]. Here's an example.

PUB MemTest | Temp
  Temp := LONG[@MyData][1]                  'Read long value
  long[@MyList][0] := Temp + $01234567      'Write long value

DAT
  MyData long 640_000, $BB50                'Long-sized/aligned data
  MyList byte long $FF995544, long 1_000    'Byte-sized/aligned long data

In this example, the DAT block (bottom of code) places its data in memory as shown in Figure 2 2. The first data element of MyData is placed at memory address $18. The last data element of MyData is placed at memory address $1C, with the first element of MyList immediately following it at $20. Note that the starting address ($18) is arbitrary and is likely to change as the code is modified or the object itself is included in another application.

Main Memory Long-Sized Data Structure and Addressing
Long Address —$18$1C$20$24
(Long Offset) —(0)(1)(2)(3)
[Long Symbol] —[MyData]   
 

Data as longs —

640,000

$BB50

$FF995544

1,000

Data as bytes —019690$50$BB$00$00$44$55$99$FF232300
 
Byte Address —$18$19$1A$1B$1C$1D$1E$1F$20$21$22$23$24$25$26$27
(Byte Offset) —(-8)(-7)(-6)(-5)(-4)(-3)(-2)(-1)(0)(1)(2)(3)(4)(5)(6)(7)
[Byte Symbol] —        [MyList]       

Near the top of the code, the first executable line of the MemTest method, Temp := long[@MyData][1], reads a long-sized value from main memory. It sets local variable Temp to $BB50; the value read from main memory address $1C. The address $1C was determined by the address of the symbol MyData ($18) plus long offset 1 (4 bytes). The following progressive simplification demonstrates this.

long[@MyData][1] → long[$18][1] → long[$18 + (1*4)] → long[$1C]

The next line, long[@MyList][0] := Temp + $01234567, writes a long-sized value to main memory. It sets the value at main memory address $20 to $012400B7. The address $20 was calculated from the address of the symbol MyList ($20) plus long offset 0 (0 bytes).

long[@MyList][0] → long[$20][0] → long[$20 + (0*4)] → long[$20]

The value $012400B7 was derived from the current value of Temp plus $01234567; $BB50 + $01234567 equals $012400B7.

Addressing Main Memory

As Figure 2 2 suggests, main memory is really just a set of contiguous bytes (see "data as bytes" row) that can also be read as longs (4-byte sets) when done properly. In fact, the above example shows that even the addresses are calculated in terms of bytes. This concept is a consistent theme for any commands that use addresses.

Main memory is ultimately addressed in terms of bytes regardless of the size of value you are accessing; byte, word, or long. This is advantageous when thinking about how bytes, words, and longs relate to each other, but it may prove problematic when thinking of multiple items of a single size, like longs.

For this reason, the LONG designator has a very handy feature to facilitate addressing from a long-centric perspective. Its BaseAddress field when combined with the optional Offset field operates in a base-aware fashion.

Imagine accessing longs of memory from a known starting point (the BaseAddress). You may naturally think of the next long or longs as being a certain distance from that point (the Offset). While those longs are indeed a certain number of "bytes" beyond a given point, it's easier to think of them as a number of "longs" beyond a point (i.e., the 4th long, rather than the long that starts beyond the 12th byte). The LONG designator treats it properly by taking the Offset value (units of longs), multiplies it by 4 (number of bytes per long), and adds that result to the BaseAddress to determine the correct long of memory to read. It also clears the lowest two bits of BaseAddress to ensure the address referenced is a long-aligned one.

So, when reading values from the MyData list, long[@MyData][0] reads the first long value and long[@MyData][1] reads the second.

If the Offset field were not used, the above statements would have to be something like long[@MyData], and long[@MyData+4], respectively. The result is the same, but the way it's written may not be as clear.
For more explanation of how data is arranged in memory, see the DAT section's Declaring Data (Syntax 1).

An Alternative Memory Reference

There is yet another way to access the data from the code example above; you could reference the data symbols directly. For example, these statements read the first two longs of the MyData list:

Temp := MyData[0]
Temp := MyData[1]

So why wouldn't you just use direct symbol references all the time? Consider the following case:

Temp := MyList[0]
Temp := MyList[1]

Referring back to the example code above Figure 2 2 you might expect these two statements to read the first and second longs of MyList; $FF995544 and 1000, respectively. Instead, it reads the first and second "bytes" of MyList, $44 and $55, respectively.

What happened? Unlike MyData, the MyList entry is defined in the code as byte-sized and byte-aligned data. The data does indeed consist of long-sized values, because each element is preceded by LONG, but since the symbol for the list is declared as byte-sized, all direct references to it will return individual bytes.

However, the LONG designator can be used instead, since the list also happens to be long-aligned because of its position following MyData.

Temp := long[@MyList][0]
Temp := long[@MyList][1]

The above reads the first long, $FF995544, followed by the second long, 1000, of MyList. This feature is very handy should a list of data need to be accessed as both bytes and longs at various times in an application.

Other Addressing Phenomena

Both the LONG and direct symbol reference techniques demonstrated above can be used to access any location in main memory, regardless of how it relates to defined data. Here are some examples:

Temp := long[@MyList][-1]        'Read last long of MyData (before MyList)
Temp := long[@MyData][2]         'Read first long of MyList (after MyData)
Temp := MyList[-8]               'Read first byte of MyData
Temp := MyData[-2]               'Read long that is two longs before MyData

These examples read beyond the logical borders (start point or end point) of the lists of data they reference. This may be a useful trick, but more often it's done by mistake; be careful when addressing memory, especially if you're writing to that memory.

Unless otherwise noted, content on this site is licensed under the
Creative Commons Attribution-ShareAlike 4.0 International License.