4. Pointers

  1. 4.1. Generic pointers
  2. 4.2. Pointers to data members
  3. 4.3. Pointers to function members

4.1. Generic pointers

TDF has no concept of a generic pointer type, so tokens are used to defer the representation of void * and the basic operations on it to the target machine. The fundamental token is:

~ptr_void : () -> SHAPE

which gives the representation of void *. This shape will be denoted by pv in the description of the following tokens. It is not guaranteed that pv is a TDF pointer shape, although normally it will be implemented as a pointer to a suitable alignment.

The token:

~null_pv : () -> EXP pv

gives the value of a null pointer of type void *. Generic pointers can also be converted to and from other pointers. These conversions are represented by the tokens:

~to_ptr_void : ( ALIGNMENT a, EXP POINTER a ) -> EXP pv
~from_ptr_void : ( ALIGNMENT a, EXP pv ) -> EXP POINTER a

where the given alignment describes the destination or source pointer type. Finally a generic pointer may be tested against the null pointer or two generic pointers may be compared. These operations are represented by the tokens:

~cpp.pv_compare : ( EXP pv, EXP pv, LABEL, NTEST ) -> EXP TOP

where the given NTEST gives the comparison to be applied and the given label gives the destination to jump to if the test fails. (Note that ~cpp.pv_compare should have been a standard C token but was omitted.)

4.2. Pointers to data members

The representation of, and operations on, pointers to data members are represented by tokens to allow for a variety of implementations. It is assumed that all pointers to data members (as opposed to pointers to function members) are represented by the same shape:

~cpp.pm.type : () -> SHAPE

This shape will be denoted by pm in the description of the following tokens.

There are two basic methods of constructing a pointer to a data member. The first is to take the address of a data member of a class. A data member is represented in TDF by an expression which gives the offset of the member from the start of its enclosing compound shape (note that it is not possible to take the address of a member of a virtual base). The mapping from this offset to a pointer to a data member is given by:

~cpp.pm.make : ( EXP OFFSET ) -> EXP pm

The second way of constructing a pointer to a data member is to use a null pointer to member:

~cpp.pm.null : () -> EXP pm

The other fundamental operation on a pointer to data member is to turn it back into an offset expression which can be added to a pointer to a class to access a member of that class in a .* or ->* operation. This is done by the token:

~cpp.pm.offset : ( EXP pm, ALIGNMENT a ) -> EXP OFFSET ( a, a )

Note that it is necessary to specify an alignment in order to describe the shape of the result. The value of this token is undefined if the given expression is a null pointer to data member.

A pointer to a data member of a non-virtual base class can be converted to a pointer to a data member of a derived class. The reverse conversion is also possible using static_cast. If the base is a primary base class then these conversions are trivial and have no effect. Otherwise null pointers to data members are converted to null pointers to data members, and the non-null cases are handled by the tokens:

~cpp.pm.cast : ( EXP pm, EXP OFFSET ) -> EXP pm
~cpp.pm.uncast : ( EXP pm, EXP OFFSET ) -> EXP pm

where the given offset is the offset of the base class within the derived class. It is also possible to convert between any two pointers to data members using reinterpret_cast. This conversion is implied by the equality of representation between any two pointers to data members and has no effect.

The only remaining operations on pointer to data members are to test one against the null pointer to data member and to compare two pointer to data members. These are represented by the tokens:

~cpp.pm.test : ( EXP pm, LABEL, NTEST ) -> EXP TOP
~cpp.pm.compare : ( EXP pm, EXP pm, LABEL, NTEST ) -> EXP TOP

where the given NTEST gives the comparison to be applied and the given label gives the destination to jump to if the test fails.

In the default implementation, pointers to data members are implemented as int. The null pointer to member is represented by 0 and the address of a class member is represented by 1 plus the offset of the member (in bytes). Casting to and from a derived class then correspond to adding or subtracting the base class offset (in bytes), and pointer to member comparisons correspond to integer comparisons.

4.3. Pointers to function members

As with pointers to data members, pointers to function members and the operations on them are represented by tokens to allow for a range of implementations. All pointers to function members are represented by the same shape:

~cpp.pmf.type : () -> SHAPE

This shape will be denoted by pmf in the description of the following tokens. Many of the tokens take an expression which has a shape which is a pointer to the alignment of pmf. This will be denoted by ppmf.

There are two basic methods for constructing a pointer to a function member. The first is to take the address of a non-static member function of a class. There are two cases, depending on whether or not the member function is virtual. The non-virtual case is given by the token:

~cpp.pmf.make : ( EXP PROC, EXP OFFSET, EXP OFFSET ) -> EXP pmf

where the first argument is the address of the corresponding function, the second argument gives any base class offset which is to be added when calling this function (to deal with inherited member functions), and the third argument is a zero offset.

For virtual functions, a pointer to function member of the form above is entered in the virtual function table for the corresponding class. The actual pointer to the virtual function member then gives a reference into the virtual function table as follows:

~cpp.pmf.vmake : ( SIGNED_NAT, EXP OFFSET, EXP, EXP ) -> EXP pmf

where the first argument gives the index of the function within the virtual function table, the second argument gives the offset of the vptr field within the class, and the third and fourth arguments are zero offsets.

The second way of constructing a pointer to a function member is to use a null pointer to function member:

~cpp.pmf.null : () -> EXP pmf
~cpp.pmf.null2 : () -> EXP pmf

For technical reasons there are two versions of this token, although they have the same value. The first token is used in static initialisers; the second token is used in other expressions.

The cast operations on pointers to function members are more complex than those on pointers to data members. The value to be cast is copied into a temporary and one of the tokens:

~cpp.pmf.cast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOP
~cpp.pmf.uncast : ( EXP ppmf, EXP OFFSET, EXP, EXP OFFSET ) -> EXP TOP

is applied to modify the value of the temporary according to the given cast. The first argument gives the address of the temporary, the second gives the base class offset to be added or subtracted, the third gives the number to be added or subtracted to convert virtual function indexes for the base class into virtual function indexes for the derived class, and the fourth gives the offset of the vptr field within the class. Again, the ability to use reinterpret_cast to convert between any two pointer to function member types arises because of the uniform representation of these types.

As with pointers to data members, there are tokens implementing comparisons on pointers to function members:

~cpp.pmf.test : ( EXP ppmf, LABEL, NTEST ) -> EXP TOP
~cpp.pmf.compare : ( EXP ppmf, EXP ppmf, LABEL, NTEST ) -> EXP TOP

Note however that the arguments are passed by reference.

The most important, and most complex, operation is calling a function through a pointer to function member. The first step is to copy the pointer to function member into a temporary. The token:

~cpp.pmf.virt : ( EXP ppmf, EXP, ALIGNMENT ) -> EXP TOP

is then applied to the temporary to convert a pointer to a virtual function member to a normal pointer to function member by looking it up in the corresponding virtual function table. The first argument gives the address of the temporary, the second gives the object to which the function is to be applied, and the third gives the alignment of the corresponding class. Now the base class conversion to be applied to the object can be determined by applying the token:

~cpp.pmf.delta : ( ALIGNMENT a, EXP ppmf ) -> EXP OFFSET ( a, a )

to the temporary to find the offset to be added. Finally the function to be called can be extracted from the temporary using the token:

~cpp.pmf.func : ( EXP ppmf ) -> EXP PROC

The function call then procedes as normal.

The default implementation is that described in the ARM, where each pointer to function member is represented in the form:

struct PTR_MEM_FUNC {
    short delta ;
    short index ;
    union {
	void ( *func ) () ;
	short off ;
    } u ;
} ;

The delta field gives the base class offset (in bytes) to be added before applying the function. The index field is 0 for null pointers, -1 for non-virtual function pointers and the index into the virtual function table for virtual function pointers (as described below these indexes start from 1). For non-virtual function pointers the function itself is given by the u.func field. For virtual function pointers the offset of the vptr field within the class is given by the u.off field.