Eventuali estensioni C degne di nota includono tipi interi il cui comportamento è indipendente dalla dimensione della parola macchina


12

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 intsono presenti 16 bit (ancora vero per molti piccoli microcontrollori) questo codice assegnerebbe un valore di 1 usando un comportamento ben definito. Su macchine con int64 bit, assegnerebbe un valore 4294836225, usando nuovamente un comportamento ben definito. Su macchine con int32 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_twrapping 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:

  1. Aggiungendo una direttiva che indicherebbe al compilatore di forzare determinati tipi di numeri interi "fondamentali" su determinate dimensioni.

  2. 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.

  3. 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 wrap32ad un intdovrebbe produrre un il risultato di tipo wrap32indipendentemente dal fatto che intsia maggiore di 16 bit, mentre l'aggiunta wrap32diretta di a wrap16dovrebbe 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:

  1. 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_tdi essere uint32_tsu compilatori dove uint32_tnon otterrebbe promossi, e la figura che è meglio per il codice che richiede wrap32_tdi 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.

  2. 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' charesecuzione di codice per macchine con un 12 bit che si aspetta unuint32_tper 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.


4
@RobertHarvey Sei sicuro? A quanto ho capito promozione a numeri interi , se intè maggiore di uint16_t, gli operandi della moltiplicazione verrebbero promossi inte la moltiplicazione verrebbe eseguita come intmoltiplicazione e il intvalore risultante verrebbe convertito in int64_tper l'inizializzazione di who_knows.

3
@RobertHarvey Come? Nel codice di OP, non si fa menzione int, eppure si insinua ancora. (Sempre supponendo che la mia comprensione dello standard C sia corretta.)

2
@RobertHarvey Certo che suona male, ma a meno che tu non riesca a sottolineare in questo modo, non stai contribuendo dicendo "nah devi fare qualcosa di sbagliato". La vera domanda è come evitare la promozione dei numeri interi o aggirare i suoi effetti!

3
@RobertHarvey: Uno degli obiettivi storici della Standards Committee C è stato quello di rendere possibile per quasi tutte le macchine di avere un "compilatore C", e hanno le regole siano sufficientemente specifica che i compilatori C in modo indipendente sviluppato per un particolare computer di destinazione sarebbe essere per lo più intercambiabili. Ciò è stato complicato dal fatto che le persone hanno iniziato a scrivere compilatori C per molte macchine prima che gli standard fossero redatti e il Comitato per gli standard non voleva vietare ai compilatori di fare qualsiasi cosa su cui il codice esistente potesse fare affidamento . Alcuni aspetti piuttosto fondamentali dello standard ...
supercat

3
... sono come non lo sono perché qualcuno ha cercato di formulare un insieme di regole "sensate", ma piuttosto perché il Comitato stava cercando di inchiodare tutte le cose che i compilatori scritti in modo indipendente già esistenti avevano in comune. Sfortunatamente, questo approccio ha portato a standard che sono allo stesso tempo troppo vaghi per consentire ai programmatori di specificare ciò che deve essere fatto, ma troppo specifico per consentire ai compilatori di "semplicemente farlo".
supercat

Risposte:


4

Come l'intenzione tipica di codice come questo

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

consiste nell'eseguire la moltiplicazione in 64 bit (la dimensione della variabile in cui viene archiviato il risultato), il modo consueto per ottenere il risultato corretto (indipendente dalla piattaforma) consiste nel lanciare uno degli operandi per forzare una moltiplicazione a 64 bit:

uint16_t ffff16 = 0xFFFF;
int64_t i_know = (int64_t)ffff16 * ffff16;

Non ho mai riscontrato estensioni C che rendono automatico questo processo.


1
La mia domanda non era come forzare la corretta valutazione di una particolare espressione aritmetica (a seconda del tipo di risultato che si desidera, o lanciare un operando uint32_toppure utilizzare una macro definita come una #define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y)))o in #define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y)))base alla dimensione di int) ma piuttosto se ci sono eventuali standard emergenti su come gestire tali cose utilmente piuttosto che definire le mie macro.
supercat

Avrei anche dovuto menzionare che, per cose come i calcoli di hash e checksum, lo scopo sarà spesso quello di prendere un risultato e troncarlo alla dimensione degli operandi. L'intenzione tipica di un'espressione simile (ushort1*ushort2) & 65535usarebbe quella di eseguire l'aritmetica mod-65536 per tutti i valori di operando. Leggendo la logica C89, penso che sia abbastanza chiaro che mentre gli autori hanno riconosciuto che tale codice potrebbe fallire su alcune implementazioni se il risultato superasse 2147483647, si aspettavano che tali implementazioni diventassero sempre più rare. Tuttavia, tale codice a volte non funziona su gcc moderno.
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.