Domanda

In C ++, sizeof ('a') == sizeof (char) == 1 . Ciò ha un senso intuitivo, poiché 'a' è un carattere letterale e sizeof (char) == 1 come definito dallo standard.

In C, tuttavia, sizeof ('a') == sizeof (int) . Cioè, sembra che i letterali dei caratteri C siano in realtà numeri interi. Qualcuno sa perché? Posso trovare molte menzioni di questa stranezza in C ma nessuna spiegazione del perché esiste.

È stato utile?

Soluzione

discussione su stesso argomento

  

" Più in particolare le promozioni integrali. In K & amp; R C era praticamente (?)   impossibile usare un valore di carattere senza prima essere promosso a int,   così facendo il carattere costante int in primo luogo ha eliminato quel passaggio.   C'erano e ci sono ancora costanti multi carattere come 'abcd' o comunque   molti si adatteranno a un int. "

Altri suggerimenti

La domanda originale è " perché? "

Il motivo è che la definizione di carattere letterale si è evoluta e cambiata, mentre cercava di rimanere retrocompatibile con il codice esistente.

Nei giorni bui dei primi C non c'erano tipi. Quando ho imparato per la prima volta a programmare in C, i tipi erano stati introdotti, ma le funzioni non avevano prototipi per dire al chiamante quali fossero i tipi di argomenti. Invece è stato standardizzato che ogni cosa passata come parametro avrebbe la dimensione di un int (questo includeva tutti i puntatori) o sarebbe stata una doppia.

Ciò significava che quando stavi scrivendo la funzione, tutti i parametri che non erano doppi venivano archiviati nello stack come ints, indipendentemente da come li hai dichiarati, e il compilatore inseriva il codice nella funzione per gestirlo.

Ciò rendeva le cose in qualche modo incoerenti, quindi quando K & amp; R scrisse il loro famoso libro, inserirono la regola che un personaggio letterale sarebbe sempre stato promosso a un int in qualsiasi espressione, non solo un parametro di funzione.

Quando il comitato ANSI ha standardizzato per la prima volta C, hanno cambiato questa regola in modo che un personaggio letterale sarebbe semplicemente un int, dal momento che questo sembrava un modo più semplice per ottenere la stessa cosa.

Durante la progettazione di C ++, tutte le funzioni dovevano avere prototipi completi (ciò non è ancora richiesto in C, sebbene sia universalmente accettato come buona pratica). Per questo motivo, è stato deciso che un personaggio letterale potesse essere archiviato in un personaggio. Il vantaggio di ciò in C ++ è che una funzione con un parametro char e una funzione con un parametro int hanno firme diverse. Questo vantaggio non è il caso in C.

Ecco perché sono diversi. Evoluzione ...

Non conosco i motivi specifici per cui un carattere letterale in C è di tipo int. Ma in C ++, c'è una buona ragione per non andare così. Considera questo:

void print(int);
void print(char);

print('a');

Ti aspetteresti che la chiamata da stampare selezioni la seconda versione che prende un carattere. Avere un personaggio letterale come un int lo renderebbe impossibile. Si noti che in C ++ i letterali con più di un carattere hanno ancora il tipo int, sebbene il loro valore sia definito dall'implementazione. Pertanto, 'ab' ha il tipo int , mentre 'a' ha il tipo char .

usando gcc sul mio MacBook, provo:

#include <stdio.h>
#define test(A) do{printf(#A":\t%i\n",sizeof(A));}while(0)
int main(void){
  test('a');
  test("a");
  test("");
  test(char);
  test(short);
  test(int);
  test(long);
  test((char)0x0);
  test((short)0x0);
  test((int)0x0);
  test((long)0x0);
  return 0;
};

che quando eseguito dà:

'a':    4
"a":    2
"":     1
char:   1
short:  2
int:    4
long:   4
(char)0x0:      1
(short)0x0:     2
(int)0x0:       4
(long)0x0:      4

che suggerisce che un personaggio è 8 bit, come sospetti, ma un carattere letterale è un int.

All'epoca della scrittura di C, il linguaggio assembly MACRO-11 del PDP-11 aveva:

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

Questo genere di cose è abbastanza comune nel linguaggio assembly: gli 8 bit bassi conterranno il codice carattere, altri bit cancellati a 0. PDP-11 aveva persino:

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

Ciò ha fornito un modo conveniente per caricare due caratteri nei byte basso e alto del registro a 16 bit. Potresti quindi scrivere quelli altrove, aggiornando alcuni dati testuali o la memoria dello schermo.

Quindi, l'idea che i personaggi vengano promossi per registrare le dimensioni è abbastanza normale e desiderabile. Ma supponiamo che sia necessario inserire 'A' in un registro non come parte del codice operativo codificato, ma da qualche parte della memoria principale contenente:

address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'

Se vuoi leggere solo una 'A' da questa memoria principale in un registro, quale vorresti leggere?

  • Alcune CPU supportano direttamente la lettura di un valore di 16 bit in un registro di 16 bit, il che significherebbe che una lettura a 20 o 22 richiederebbe quindi la cancellazione dei bit da 'X' e in base all'endianness della CPU l'una o l'altra avrebbe bisogno di spostarsi nel byte di ordine inferiore.

  • Alcune CPU potrebbero richiedere una lettura allineata alla memoria, il che significa che l'indirizzo più basso coinvolto deve essere un multiplo della dimensione dei dati: potresti essere in grado di leggere dagli indirizzi 24 e 25, ma non 27 e 28.

Quindi, un compilatore che genera codice per ottenere una 'A' nel registro potrebbe preferire sprecare un po 'di memoria aggiuntiva e codificare il valore come 0' A 'o' A '0 - a seconda dell'endianness, e anche assicurarsi che sia allineato correttamente (cioè non con un indirizzo di memoria dispari).

La mia ipotesi è che C abbia semplicemente portato questo livello di comportamento incentrato sulla CPU, pensando a costanti di caratteri che occupano dimensioni di registro della memoria, portando avanti la valutazione comune di C come un "assemblatore di alto livello".

(Vedi 6.3.3 a pagina 6-25 di http: //www.dmv .net / DEC / pdf / macro.pdf )

Ricordo di aver letto K & amp; R e di aver visto uno snippet di codice che avrebbe letto un personaggio alla volta fino a quando non avesse colpito EOF. Poiché tutti i caratteri sono caratteri validi per essere in un flusso di file / input, ciò significa che EOF non può avere alcun valore char. Quello che ha fatto il codice è stato quello di mettere il carattere letto in un int, quindi testare EOF, quindi convertirlo in un carattere se non lo era.

Mi rendo conto che questo non risponde esattamente alla tua domanda, ma avrebbe senso che il resto dei letterali dei personaggi fosse sizeof (int) se fosse letterale EOF.

int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;

while ((r = getc(file)) != EOF)
{
  *(p++) = (char) r;
}

Non ne ho visto una logica (i caratteri letterali in C sono tipi int), ma ecco qualcosa che Stroustrup ha dovuto dire al riguardo (da Design ed Evolution 11.2.1 - Risoluzione fine dei grani):

  

In C, il tipo di carattere letterale come 'a' è int .   Sorprendentemente, dare 'un' tipo char in C ++ non causa alcun problema di compatibilità.   Ad eccezione dell'esempio patologico sizeof ('a') , ogni costrutto che può essere espresso   sia in C che in C ++ dà lo stesso risultato.

Quindi, per la maggior parte, non dovrebbe causare problemi.

Questo è il comportamento corretto, chiamato "promozione integrale". Può succedere anche in altri casi (principalmente operatori binari, se ricordo bene).

EDIT: Solo per essere sicuro, ho controllato la mia copia di Expert C Programming: Deep Secrets e ho confermato che un carattere letterale non inizia con un tipo < strong> int . Inizialmente è di tipo carattere ma quando viene utilizzato in un'espressione , viene promosso in un int . Quanto segue è citato dal libro:

  

I letterali dei caratteri hanno tipo int e   ci arrivano seguendo le regole   per la promozione dal tipo char. Questo è   trattato troppo brevemente in K & amp; R 1, a pagina   39 dove dice:

     

Ogni carattere in un'espressione è   convertito in un int .... Notare che   tutti i float in un'espressione lo sono   convertito in doppio .... Dal momento che a   l'argomento della funzione è un'espressione,   le conversioni di tipo avvengono anche quando   gli argomenti vengono passati alle funzioni: in   particolare, char e short diventano int,   float diventa double.

La ragione storica di ciò è che C, e il suo predecessore B, erano originariamente sviluppati su vari modelli di minicomputer DEC PDP con varie dimensioni di parole, che supportavano ASCII a 8 bit ma potevano eseguire solo aritmetica sui registri. (Non il PDP-11, tuttavia; che è venuto dopo.) Le prime versioni di C definivano int come la dimensione della parola nativa della macchina e qualsiasi valore inferiore a un int doveva essere ampliato a int per essere passato ao da una funzione, oppure usato in un'espressione logica, aritmetica o bit per bit, perché era così che funzionava l'hardware sottostante.

Questo è anche il motivo per cui le regole di promozione dei numeri interi dicono ancora che qualsiasi tipo di dati più piccolo di un int è promosso in int . Le implementazioni in C sono anche autorizzate a usare una matematica con complemento a S invece di due complementi a S per ragioni storiche simili. La ragione per cui il carattere ottale fuoriesce e le costanti ottali sono cittadini di prima classe rispetto all'esagono è parimenti che quei primi minicomputer DEC avevano dimensioni delle parole divisibili in blocchi di tre byte ma non crocchette di quattro byte.

Non lo so, ma indovinerò che è stato più semplice implementarlo in quel modo e non importava. Non è stato fino a C ++ quando il tipo poteva determinare quale funzione sarebbe stata chiamata che doveva essere riparata.

Non lo sapevo davvero. Prima che esistessero i prototipi, qualcosa di più stretto di un int veniva convertito in un int quando lo utilizzava come argomento di funzione. Ciò può far parte della spiegazione.

Questo è solo tangenziale alle specifiche del linguaggio, ma nell'hardware la CPU di solito ha solo una dimensione di registro - diciamo 32 bit - e quindi ogni volta che funziona su un carattere (aggiungendolo, sottraendolo o confrontandolo ) c'è una conversione implicita in int quando viene caricata nel registro. Il compilatore si occupa di mascherare e spostare correttamente il numero dopo ogni operazione in modo che se aggiungi, diciamo, 2 a (carattere senza segno) 254, si avvolgerà intorno a 0 invece di 256, ma all'interno del silicio è davvero un int fino a quando non lo salvi di nuovo in memoria.

È una specie di punto accademico perché il linguaggio avrebbe potuto comunque specificare un tipo letterale a 8 bit, ma in questo caso le specifiche del linguaggio riflettono più da vicino ciò che la CPU sta realmente facendo.

(i winks x86 possono notare che esiste un es. un addh op nativo che aggiunge i registri a corto raggio in un passaggio, ma all'interno del core RISC questo si traduce in due passaggi: aggiungere i numeri, quindi estendi segno, come una coppia add / extsh su PowerPC)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top