A livello astratto, puoi includere tutto ciò che vuoi in una lingua che stai progettando.
A livello di implementazione, è inevitabile che alcune di queste cose siano più semplici da implementare, alcune saranno complicate, alcune possono essere velocizzate, altre sono più lente e così via. Per tener conto di ciò, i progettisti devono spesso prendere decisioni difficili e compromessi.
A livello di implementazione, uno dei modi più veloci che abbiamo trovato per accedere a una variabile è scoprire il suo indirizzo e caricare il contenuto di quell'indirizzo. Ci sono istruzioni specifiche nella maggior parte delle CPU per caricare i dati dagli indirizzi e quelle istruzioni di solito devono sapere quanti byte devono caricare (uno, due, quattro, otto, ecc.) E dove mettere i dati che caricano (registro singolo, registro coppia, registro esteso, altra memoria, ecc.). Conoscendo la dimensione di una variabile, il compilatore può sapere esattamente quale istruzione emettere per gli usi di quella variabile. Non conoscendo la dimensione di una variabile, il compilatore dovrebbe ricorrere a qualcosa di più complicato e probabilmente più lento.
A livello astratto, il punto di sottotipizzazione deve essere in grado di utilizzare istanze di un tipo in cui è previsto un tipo uguale o più generale. In altre parole, il codice può essere scritto che si aspetta un oggetto di un tipo particolare o qualcosa di più derivato, senza sapere in anticipo quale sarebbe esattamente questo. E chiaramente, poiché più tipi derivati possono aggiungere più membri dati, un tipo derivato non ha necessariamente gli stessi requisiti di memoria dei suoi tipi base.
A livello di implementazione, non esiste un modo semplice per una variabile di una dimensione predeterminata di contenere un'istanza di dimensioni sconosciute e di accedere a un sistema che normalmente si chiama efficiente. Ma c'è un modo per spostare le cose un po 'e usare una variabile per non memorizzare l'oggetto, ma per identificare l'oggetto e lasciare che l'oggetto venga memorizzato da qualche altra parte. In questo modo è un riferimento (ad esempio un indirizzo di memoria) - un livello aggiuntivo di riferimento indiretto che assicura che una variabile abbia solo bisogno di contenere qualche tipo di informazione a dimensione fissa, purché possiamo trovare l'oggetto attraverso tali informazioni. Per riuscirci, abbiamo solo bisogno di caricare l'indirizzo (dimensione fissa) e quindi possiamo lavorare come al solito usando gli offset dell'oggetto che sappiamo essere validi, anche se quell'oggetto ha più dati sugli offset che non conosciamo. Possiamo farlo perché non ci occupiamo più dei suoi requisiti di archiviazione quando accediamo ad esso.
A livello astratto, questo metodo consente di memorizzare un (riferimento a a) string
in una variabile object
senza perdere le informazioni che lo rendono un string
. Va bene che tutti i tipi funzionino così e potresti anche dire che è elegante sotto molti aspetti.
Tuttavia, a livello di implementazione, il livello extra di indirizzamento indiretto richiede più istruzioni e sulla maggior parte delle architetture rende ogni accesso all'oggetto un po 'più lento. Puoi consentire al compilatore di spremere più prestazioni da un programma se includi nella tua lingua alcuni tipi comunemente usati che non hanno quel livello aggiuntivo di riferimento indiretto (il riferimento). Ma rimuovendo quel livello di riferimento indiretto, il compilatore non può più permettervi di sottotitolare in un modo sicuro per la memoria. Questo perché se aggiungi altri membri di dati al tuo tipo e assegni a un tipo più generale, tutti i membri di dati aggiuntivi che non rientrano nello spazio allocato per la variabile di destinazione verranno tagliati via.