Most compilers allocate space for arrays in a straightforward way. Let us consider the following declaration where TYPEX
is the name of a type, and BUFLEN
is a natural number.
TYPEX buffer[BUFLEN];
The total number number of bytes used by variable buffer
is sizeof(TYPEX)*BUFLEN
.
In terms of address (for byte-addressable architectures), &buffer[i]
has an address of &buffer + sizeof(TYPEX) * i
.
General indexing is difficult in TTPASM because of the lack of a multiplication instruction. However, indexing into an array of byte-size elements is easy. The following is an example:
// assume the address of an array is in register c
// assume the index is in register b
// assume an element is one byte wide
add b,c // b is now the address of the element at the index
If the size of an element is a power of 2, the code is still relatively simple:
// assume the address of an array is in register c
// assume the index is in register b
// assume an element is four-byte wide
add b,b // b=2*b
add b,b // b=2*b, now it is 4x the initial value
add b,c // b is now the address of the element at the index
However, there is no easy way to generate a template of code given the size of each array element.
Pointer arithmetic is generally as difficult. However, pointer increment can be done easily. Consider the following C code:
TYPEX *ptr;
//...
ptr+1 // compute the value of this expression
The equivalent TTPASM code is as follows:
// assume the value of ptr is in register a
// assume the size of TYPEX is defined by the label TYPEX_size
ldi b,TYPEX_size
add a,b // a is now the value of ptr+1
Many algorithms related to arrays only need to access elements sequentially. As a result, the code can be converted to use pointer-arithmetic operations. For example, the following C code computes the sum of an array:
for (i = 0, sum = 0; i < N; ++i)
{
sum += a[I];
}
It can be changed to use pointer arithmetic as follows:
for (i=0, sum=0, ptr=a; i<N; ++i)
{
sum += *(ptr++);
}
Note that the expression sum += *(ptr++)
is the same as first performing sum += *ptr
, then performing ptr++
.
A general structure definition in C/C++ looks like this:
struct STRUCTX
{
TYPE1 m1; // first member
TYPE2 m2;
// ...
TYPEn mn; // last member
};
The word width of an architecture is the width of the data bus in the processor core. Currently, most production processors have a word-width of 64 bits, also known as 8 bytes. If a member is of a scalar type, then a compiler attempts to make sure the entire member can be accessed in a single memory operation based on the word width of the processor.
Unless otherwise instructed using a pragma
, a compiler sequentially allocates storage for members within a structure. The offset of a member from the beginning of a structure is based on the offset of a previous member, but aligned to ensure each elemental type can be accessed in a single memory cycle.
The offset to the first member is easy to compute because it is at the beginning of the entire structure, the offset is 0 (zero).
The offsets to the rest of the members are a little more complicated. Let offset(m)
refer to the byte offset to member m
in a structure, and alignment(m)
refers to the alignment width of member m
(to be defined). Then $\mathtt{offset}(\mathtt{m}_{i+1}) = \lceil \frac{\mathtt{offset}(m_i)+\mathtt{sizeof}(\mathtt{m}_i)}{\mathtt{alignment}(\mathtt{m}_{i+1})} \rceil \times \mathtt{alignment}(\mathtt{m}_{i+1})$. The symbol $\lceil x \rceil$ is the ceiling function that returns the smallest integer greater than or equal to $x$.
The alignment width of a structure is the maximum of the alignments of its members but is also restricted by the architecture’s word width (in bytes). In this example, alignment(STRUCTX) = min(wordWidth, max(alignment(m1), alignment(m2),... alignment(mn)))
. For a 64-bit architecture, wordWidth
is 8.
The alignment width of an elemental (scalar) type t
is defined as alignment(t)=min(wordWidth, sizeof(t))
.
TTP has a wordWidth
of 1. As a result, there are no alignment issues!
The concept of a struct
is merely a matter of tracking the offset from the beginning of a structure to the members, please an overall size of a struct
.
For example, let us consider the following C struct
definition:
struct X
{
uint8_t x;
struct X *ptr;
uint8_t y;
};
This translates to the following labels in TTPASM:
X_x: 0
X_ptr: X_x 1 +
X_y: X_ptr 1 +
X_size: X_y 1 +
Because labels are used to track the offset of a member from the beginning of a structure, given the address of a structure is in a register already, the address of a member is the sum of the address of the structure and the offset to the member. Using the example from the previous section, the following is an example:
// assume the address of a struct X is in register b
ldi a,X_y
add b,a // b is now the address of member y of the struct X