Una caratteristica interessante di C rispetto ad alcuni altri linguaggi è che molti dei suoi tipi di dati si basano sulla dimensione delle parole dell'architettura target, piuttosto che essere specificati in termini assoluti. Sebbene ciò consenta di utilizzare il linguaggio per scrivere codice su macchine che potrebbero avere difficoltà con determinati tipi, rende molto difficile progettare codice che verrà eseguito in modo coerente su architetture diverse. Considera il codice:
uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;
Su un'architettura in cui int
sono presenti 16 bit (ancora vero per molti piccoli microcontrollori) questo codice assegnerebbe un valore di 1 usando un comportamento ben definito. Su macchine con int
64 bit, assegnerebbe un valore 4294836225, usando nuovamente un comportamento ben definito. Su macchine con int
32 bit, probabilmente assegnerebbe un valore di -131071 (non so se sarebbe un comportamento definito dall'implementazione o definito). Anche se il codice non usa altro che quelli che si presume siano tipi di "dimensioni fisse", lo standard richiederebbe che due diversi tipi di compilatore in uso oggi produrrebbero due risultati diversi e molti compilatori popolari oggi ne produrrebbero un terzo.
Questo esempio particolare è in qualche modo inventato, in quanto non mi aspetterei che nel codice del mondo reale assegni il prodotto di due valori a 16 bit direttamente a un valore a 64 bit, ma è stato scelto come breve esempio per mostrare tre numeri interi le promozioni possono interagire con tipi senza segno di dimensioni presumibilmente fisse. Ci sono alcune situazioni del mondo reale in cui è necessario che la matematica sui tipi senza segno sia eseguita secondo le regole dell'aritmetica dei numeri matematici, altre in cui è necessario che sia eseguita secondo le regole dell'aritmetica modulare, e altre in cui non funziona davvero non importa. Un sacco di codice del mondo reale per cose come i checksum si basa sul uint32_t
wrapping aritmetico mod 2³² e sulla capacità di eseguire arbitrariamenteuint16_t
aritmetica e ottenere risultati che sono, come minimo, definiti come mod accurati 65536 (invece di innescare un comportamento indefinito).
Anche se questa situazione sembrerebbe chiaramente indesiderabile (e lo sarà ancora di più poiché l'elaborazione a 64 bit diventa la norma per molti scopi), il comitato per gli standard C da quello che ho osservato preferisce introdurre funzionalità linguistiche che sono già utilizzate in una notevole produzione ambienti, piuttosto che inventarli "da zero". Esistono notevoli estensioni al linguaggio C che consentirebbero al codice di specificare non solo come verrà archiviato un tipo, ma anche come dovrebbe comportarsi in scenari che coinvolgono possibili promozioni? Vedo almeno tre modi in cui un'estensione del compilatore potrebbe risolvere tali problemi:
Aggiungendo una direttiva che indicherebbe al compilatore di forzare determinati tipi di numeri interi "fondamentali" su determinate dimensioni.
Aggiungendo una direttiva che istruisce il compilatore a valutare vari scenari di promozione come se i tipi di macchina avessero dimensioni particolari, indipendentemente dalle dimensioni effettive dei tipi nell'architettura di destinazione.
Consentendo mezzi per dichiarare tipi con caratteristiche specifiche (ad es. Dichiarare che un tipo dovrebbe comportarsi come un anello algebrico avvolgente mod-65536, indipendentemente dalla dimensione della parola sottostante, e non dovrebbe essere implicitamente convertibile in altri tipi; l'aggiunta di un
wrap32
ad unint
dovrebbe produrre un il risultato di tipowrap32
indipendentemente dal fatto cheint
sia maggiore di 16 bit, mentre l'aggiuntawrap32
diretta di awrap16
dovrebbe essere illegale (poiché nessuno dei due potrebbe convertirsi nell'altro).
La mia preferenza sarebbe la terza alternativa, poiché consentirebbe anche alle macchine con dimensioni di parole insolite di lavorare con un sacco di codice che prevede che le variabili si "avvolgano" come farebbero con una potenza di due dimensioni; il compilatore potrebbe dover aggiungere istruzioni di mascheramento dei bit per far si che il tipo si comporti in modo adeguato, ma se il codice necessita di un tipo che avvolge la mod 65536, è meglio che il compilatore generi tale mascheramento su macchine che ne hanno bisogno piuttosto che ingombrare il codice sorgente con esso o semplicemente avere tale codice inutilizzabile su macchine in cui sarebbe necessario tale mascheramento. Sono curioso, tuttavia, se ci sono estensioni comuni che potrebbero raggiungere un comportamento portatile con uno dei mezzi sopra indicati o con alcuni mezzi a cui non ho pensato.
Per chiarire cosa sto cercando, ci sono alcune cose; soprattutto:
Mentre ci sono molti modi in cui il codice può essere scritto in modo da garantire la semantica desiderata (ad esempio, la definizione di macro da eseguire fa matematica su operandi non firmati di dimensioni particolari in modo da produrre un risultato che si avvolge esplicitamente o no) o almeno impedisce indesiderate semantica (es condizionatamente-definiscono un tipo
wrap32_t
di essereuint32_t
su compilatori doveuint32_t
non otterrebbe promossi, e la figura che è meglio per il codice che richiedewrap32_t
di sicuro compilazione su macchine dove quel tipo otterrebbe promossi che per fare eseguire e resa comportamento falso), se esiste un modo per scrivere il codice che giocherebbe più favorevolmente con le future estensioni del linguaggio, usarlo sarebbe meglio che elaborare il mio approccio.Ho alcune idee piuttosto solide su come il linguaggio potrebbe essere esteso in modo da risolvere molti problemi di dimensioni intere, consentendo al codice di produrre semantica identica su macchine con dimensioni di parole diverse, ma prima di dedicare un tempo significativo alla loro scrittura, mi piacerebbe per sapere quali sforzi in quella direzione sono già stati intrapresi.
Non desidero in alcun modo essere visto come denigratorio del C Standards Committee o del lavoro che hanno prodotto; Mi aspetto, tuttavia, che entro pochi anni sarà necessario far funzionare correttamente il codice su macchine in cui il tipo di promozione "naturale" sarebbe 32 bit, così come quelli in cui sarebbe 64 bit. Penso che con alcune modeste estensioni del linguaggio (più modeste di molte altre modifiche tra C99 e C14) sarebbe possibile non solo fornire un modo pulito di utilizzare in modo efficiente architetture a 64 bit, ma nel contrattare facilitando anche l'interazione con le macchine "di dimensioni insolite di parole" che lo standard ha storicamente piegato all'indietro per supportare [ad esempio, rendendo possibile l' char
esecuzione di codice per macchine con un 12 bit che si aspetta unuint32_t
per avvolgere mod 2³²]. A seconda della direzione che prenderanno le future estensioni, mi aspetterei anche che sia possibile definire macro che consentano al codice scritto oggi di essere utilizzabile sui compilatori di oggi in cui i tipi di numeri interi predefiniti si comportano come "previsti", ma anche utilizzabili su compilatori futuri in cui numeri interi i tipi sarebbero di default si comportano diversamente, ma dove possono fornire i comportamenti richiesti.
int
, eppure si insinua ancora. (Sempre supponendo che la mia comprensione dello standard C sia corretta.)
int
è maggiore diuint16_t
, gli operandi della moltiplicazione verrebbero promossiint
e la moltiplicazione verrebbe eseguita comeint
moltiplicazione e ilint
valore risultante verrebbe convertito inint64_t
per l'inizializzazione diwho_knows
.