Programmazione orientata agli oggetti in ANSI-C. Evitare la ricorsione

Nella sezione 8.3 abbiamo cercato di implementare cast() come segue:

% cast {
assert(isOf(_self, class));
return (void *) _self;
}

Sfortunatamente, ciò crea un loop infinito. Per capire come, usiamo la seguente traccia di chiamate:

void * list = new(List, 1);
void * object = new(Object);
addFirst(list, object) {
cast(List, list) {
isOf(list, List) {
classOf(list) {
cast(Object, list) {
ifOf(list, Object) {
classOf(list) {

cast() è basata su isOf() che chiama classOf e possibilmente super(). Entrambi i metodi seguono la nostra codifica standard e importano i loro parametri con %casts, che a turno chiama cast() per verificare se gli argomenti siano Oggetto o Classi rispettivamente. La nostra implementazione di isOf() nella sezione 8.3 chiama classOf() prima di osservare il terzo assioma che ogni oggetto appartenga almeno ad Object.

Quanto deve essere stringente il controllo sui tipi? Se ci fidiamo del nostro codice, cast() è una no-op e potrebbe essere sostituita da una macro triviale. Se non ci fidiamo a pieno del nostro codice, i parametri e tutte le operazione di dereferencing devono essere verificate e maneggiate attraverso cast(). Tutti devono quindi usare e confidare su cast() e chiaramente cast() non può usare altre funzioni per svolgere il proprio compito.

Pertanto, cosa garantisce cast(class, object)? Garantisce almeno la stessa cosa di isOf(), ossia che il suo object non è un puntatore nullo e che la sua descrizione di classe può essere tracciata con l'argomento class. Se prendiamo il codice di isOf() e pensiamo “sulla difensiva” otteniamo il seguente algoritmo:

(_self = self) is an object
(myClass = self —> class) is an object
if (class != Object)
class is an object
while (myClass != class)
assert(myClass != Object);
myClass is a class description
myClass = myClass —> super;
return self;

Le parti critiche sono in corsivo: quale puntatore nonzero rappresenta un oggetto? Come riconosciamo una descrizione di classe? Un modo per distinguere puntatori arbitrari da un puntatore ad oggetti è permettere che ogni oggetto inizi con un numero magico, ossia aggiungere una componente .magic alla descrizione di classe in Object.d

% Class Object {
unsigned long magic; // magic number
const Class @ class; // object’s description
%
...

Una volta che il numero magico viene impostato da new() e inizializzato in Class e Object.

Parlando chiaro, non abbiamo la necessità di controllare che myClass sia un oggetto ma le due asserzioni. Se non controlliamo che class sia un oggetto potrebbe essere un puntatore null e quindi potremmo passare un oggetto con un puntatore null a cast().

#define MAGIC 0x0effaced // magic number for objects
// efface: to make (oneself) modestly or shyly inconspicuous
#define isObject(p) \
( assert(p), \
assert(((struct Object *) p) —> magic == MAGIC), p )

La parte costosa è invece determinare se myClass sia una descrizione di classe. Non dovremmo avere molte descrizioni di classe nei nostri progetti (dipende tuttavia dalla complessità) e dovremmo conoscerle tutte a menadito, così potremmo consultare velocemente una tabella di puntatori validi. Tuttavia, cast() è una delle funzioni cardine del nostro codice, pertanto dovrebbe essere resa il più efficiente possibile. Per incominciare, myClass è il secondo elemento in una catena da un oggetto alla sua descrizione di classe ed entrambi sono stati controllati in modo che contengano questo numero magico. Supponiamo che sia lecito aspettarsi che la catena .super fra le descrizioni di classe rimanga invariata dopo che Class_ctor() sia stato impostato. Quindi, possiamo rimuovere il test dal loop e arrivare alla seguente implementazione di cast():

static void catch (int sig) // signal handler: bad pointer
{
assert(sig == 0); // bad pointer, should not happen
}
% cast {
void (* sigsegv)(int) = signal(SIGSEGV, catch);
#ifdef SIGBUS
void (* sigbus)(int) = signal(SIGBUS, catch);
#endif
const struct Object * self = isObject(_self);
const struct Class * myClass = isObject(self —> class);
if (class != Object)
{ isObject(class);
while (myClass != class)
{ assert(myClass != Object); // illegal cast
myClass = myClass —> super;
}
}
#ifdef SIGBUS
signal(SIGBUS, sigbus);
#endif
signal(SIGSEGV, sigsegv);
return (void *) self;
}

Il processing dei segnali ci protegge da errori nei valori numerici dei puntatori. SIG-SEGV è definito da ANSI-C ed indica un accesso illegale alla memoria, SIGBUS (o _SIGBUS) è il secondo di questi segnali definito in moltissimi sistemi.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend