Perché i caratteri letterali C sono interi anziché caratteri?


103

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

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


sizeof restituirebbe solo la dimensione di un byte, no? Non sono un char e un int di dimensioni uguali?
Josh Smeaton,

1
Questo dipende probabilmente dal compilatore (e dall'architettura). Ti va di dire cosa stai usando? Lo standard (almeno fino all'89) era molto sciolto.
dmckee --- gattino ex moderatore

2
no. un carattere è sempre grande 1 byte, quindi sizeof ('a') == 1 sempre (in c ++), mentre un int può teoricamente avere dimensioni pari a 1, ma ciò richiederebbe un byte con almeno 16 bit, il che è molto improbabile: ) quindi sizeof ('a')! = sizeof (int) è molto probabile in C ++ nella maggior parte delle implementazioni
Johannes Schaub - litb

2
... mentre è sempre sbagliato in C.
Johannes Schaub - litb

22
'a' è un int in C - period. C è arrivato prima - C ha stabilito le regole. Il C ++ ha cambiato le regole. Si può sostenere che le regole del C ++ hanno più senso, ma cambiare le regole del C farebbe più danni che benefici, quindi il comitato dello standard C saggiamente non l'ha toccato.
Jonathan Leffler,

Risposte:


36

discussione sullo stesso argomento

"Più specificamente le promozioni integrali. In K&R C era virtualmente (?) Impossibile usare un valore di carattere senza che fosse prima promosso a int, quindi rendere il carattere costante int in primo luogo ha eliminato quel passaggio. C'erano e sono ancora multi carattere costanti come 'abcd' o comunque molte si adatteranno a un int. "


Le costanti multi-carattere non sono portabili, anche tra compilatori su una singola macchina (sebbene GCC sembri essere coerente su tutte le piattaforme). Vedi: stackoverflow.com/questions/328215
Jonathan Leffler

8
Vorrei notare che a) Questa citazione non è attribuita; la citazione dice semplicemente "Non saresti d'accordo con questa opinione, che è stata pubblicata in un thread precedente che discuteva la questione in questione?" ... eb) È ridicolo , perché una charvariabile non è un int, quindi rendere un carattere costante come uno è un caso speciale. Ed è facile da usare un valore di carattere, senza promuoverlo: c1 = c2;. OTOH, c1 = 'x'è una conversione al ribasso. Ancora più importante, sizeof(char) != sizeof('x')che è un grave pasticcio linguistico. Per quanto riguarda le costanti di carattere multibyte: sono il motivo, ma sono obsolete.
Jim Balter

27

La domanda originale è "perché?"

Il motivo è che la definizione di un carattere letterale si è evoluta e cambiata, cercando di rimanere compatibile all'indietro con il codice esistente.

Nei giorni bui dell'inizio del C non c'erano affatto tipi. Quando ho imparato a programmare in C, i tipi erano stati introdotti, ma le funzioni non avevano prototipi per dire al chiamante quali fossero i tipi di argomento. Invece è stato standardizzato che tutto ciò che viene passato come parametro avrebbe o la dimensione di un int (questo includeva tutti i puntatori) o sarebbe stato un double.

Ciò significava che quando stavi scrivendo la funzione, tutti i parametri che non erano double erano memorizzati nello stack come int, indipendentemente da come li hai dichiarati, e il compilatore ha inserito il codice nella funzione per gestirlo per te.

Ciò ha reso le cose in qualche modo incoerenti, quindi quando K&R ha scritto il loro famoso libro, hanno stabilito la regola che un carattere letterale sarebbe sempre stato promosso a int in qualsiasi espressione, non solo a un parametro di funzione.

Quando il comitato ANSI ha standardizzato per la prima volta C, ha cambiato questa regola in modo che un carattere letterale fosse semplicemente un int, poiché questo sembrava un modo più semplice per ottenere la stessa cosa.

Quando è stato progettato il C ++, tutte le funzioni dovevano avere prototipi completi (questo non è ancora richiesto in C, sebbene sia universalmente accettato come buona pratica). Per questo motivo, è stato deciso che un carattere letterale potesse essere memorizzato in un carattere. 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 di C.

Questo è il motivo per cui sono diversi. Evoluzione...


2
+1 da parte mia per aver effettivamente risposto "perché?". Ma non sono d'accordo con l'ultima affermazione - "Il vantaggio di questo in C ++ è che una funzione con un parametro char e una funzione con un parametro int hanno firme diverse" - in C ++ è ancora possibile che 2 funzioni abbiano parametri di stessa dimensione e firme diverse, ad esempio void f(unsigned char)Vs void f(signed char).
Peter K

3
@ PeterK John avrebbe potuto metterlo meglio, ma quello che dice è essenzialmente accurato. La motivazione per il cambiamento in C ++ era, se scrivi f('a'), probabilmente vuoi che la risoluzione dell'overload scelga f(char)per quella chiamata piuttosto che f(int). Le dimensioni relative di inte charnon sono rilevanti, come dici tu.
Zwol

21

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 in quel modo. Considera questo:

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

print('a');

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


Sì, "Progettazione ed evoluzione del C ++" afferma che le routine di input / output sovraccariche sono state la ragione principale per cui C ++ ha cambiato le regole.
Max Lybbert,

5
Max, sì, ho tradito. ho guardato nello standard nella sezione compatibilità :)
Johannes Schaub - litb

18

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 carattere è di 8 bit, come sospetti, ma un carattere letterale è un int.


7
+1 per essere interessante. Le persone spesso pensano che sizeof ("a") e sizeof ("") siano caratteri * e dovrebbero dare 4 (o 8). Ma in realtà sono char [] a quel punto (sizeof (char [11]) dà 11). Una trappola per i neofiti.
paxdiablo

3
Un carattere letterale non viene promosso a un int, è già un int. Non c'è alcuna promozione in corso se l'oggetto è un operando dell'operatore sizeof. Se ci fosse, questo vanificherebbe lo scopo di sizeof.
Chris Young,

@ Chris Young: Ya. Dai un'occhiata. Grazie.
dmckee --- gattino ex moderatore

8

Ai tempi in cui si scriveva 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, gli altri bit cancellati a 0. PDP-11 aveva anche:

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 scriverli altrove, aggiornando alcuni dati testuali o la memoria dello schermo.

Quindi, l'idea che i personaggi vengano promossi alla dimensione di registro è abbastanza normale e desiderabile. Ma diciamo che devi inserire 'A' in un registro non come parte dell'opcode hardcoded, ma da qualche parte nella 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 'LA' da questa memoria principale in un registro, quale leggeresti?

  • Alcune CPU possono supportare direttamente la lettura di un valore a 16 bit in un registro a 16 bit, il che significherebbe che una lettura a 20 o 22 richiederebbe quindi la cancellazione dei bit da "X" e, a seconda dell'endianness della CPU, l'uno o l'altro 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 extra e codificare il valore come 0' A 'o' A '0 - a seconda dell'endianness, e assicurandosi anche che sia allineato correttamente ( cioè non a un indirizzo di memoria dispari).

La mia ipotesi è che i C abbiano semplicemente trasferito questo livello di comportamento incentrato sulla CPU, pensando alle costanti dei caratteri che occupano dimensioni di registro della memoria, sostenendo la valutazione comune di C come un "assemblatore di alto livello".

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


5

Ricordo di aver letto K&R e di aver visto uno snippet di codice che leggeva un carattere alla volta fino a quando non arrivava a EOF. Poiché tutti i caratteri sono caratteri validi per essere in un file / flusso di input, ciò significa che EOF non può essere alcun valore di carattere. Ciò che il codice ha fatto è stato inserire 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 caratteri letterali fosse sizeof (int) se il letterale EOF fosse.

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

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

Tuttavia, non penso che 0 sia un carattere valido.
gbjbaanb

3
@gbjbaanb: Certo che lo è. È il carattere nullo. Pensaci. Pensi che a un file non dovrebbe essere consentito di contenere zero byte?
P Daddy,

1
Leggi wikipedia - "Il valore effettivo di EOF è un numero negativo dipendente dal sistema, comunemente -1, che è garantito essere diverso da qualsiasi codice carattere valido."
Malx,

2
Come dice Malx - EOF non è un tipo char - è un tipo int. getchar () e gli amici restituiscono un int, che può contenere qualsiasi carattere così come EOF senza conflitti. Ciò non richiederebbe davvero che i caratteri letterali abbiano il tipo int.
Michael Burr

2
EOF == -1 è venuto molto dopo le costanti del carattere di C, quindi questa non è una risposta e non è nemmeno rilevante.
Jim Balter

5

Non ho visto una logica per questo (i caratteri C char sono tipi int), ma ecco qualcosa che Stroustrup ha avuto da dire al riguardo (da Design and Evolution 11.2.1 - Risoluzione a grana fine):

In C, il tipo di un carattere letterale come 'a'è int. Sorprendentemente, dare il 'a'tipo charin C ++ non causa problemi di compatibilità. Fatta eccezione per l'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.


Interessante! Un po 'contraddice ciò che altri stavano dicendo su come il comitato per gli standard C abbia "saggiamente" deciso di non rimuovere questa stranezza da C.
j_random_hacker

2

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

Questo è anche il motivo per cui le regole di promozione dei numeri interi dicono ancora che intviene promosso qualsiasi tipo di dati più piccolo di an int. Le implementazioni C possono anche usare la matematica con complemento a uno invece del complemento a due per ragioni storiche simili. La ragione per cui i caratteri ottali sfuggono e le costanti ottali sono cittadini di prima classe rispetto a esadecimali è allo stesso modo che quei primi minicomputer DEC avevano dimensioni delle parole divisibili in blocchi di tre byte ma non nibble di quattro byte.


... ed charera esattamente lungo 3 cifre ottali
Antti Haapala

1

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

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

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

Ogni carattere in un'espressione viene convertito in un int .... Si noti che tutti i float in un'espressione vengono convertiti in double .... Poiché l'argomento di una 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.


Se bisogna credere agli altri commenti, l'espressione 'a' inizia con il tipo int - nessuna promozione di tipo viene eseguita all'interno di sizeof (). Quella "a" ha tipo int è solo una stranezza di C a quanto pare.
j_random_hacker

2
Un letterale char ha avere tipo int. Lo standard ANSI / ISO 99 le chiama "costanti di caratteri interi" (per differenziarle dalle "costanti di caratteri larghi", che hanno tipo wchar_t) e in particolare dice: "Una costante di caratteri interi ha tipo int".
Michael Burr,

Quello che volevo dire era che non inizia con il tipo int, ma piuttosto convertito in un int da char (risposta modificata). Naturalmente, questo probabilmente non riguarda nessuno tranne gli autori di compilatori poiché la conversione viene sempre eseguita.
PolyThinker

3
No! Se leggi lo standard ANSI / ISO 99 C troverai che in C l'espressione 'a' inizia con il tipo int. Se si dispone di una funzione f void (int) e una variabile char c, allora f (c) si eseguire promozione integrale, ma f ( 'a') non sarà come il tipo di 'a' è già int. Strano ma vero.
j_random_hacker

2
"Giusto per essere sicuri" - Potresti esserne più sicuro leggendo effettivamente l'affermazione: "I caratteri letterali hanno tipo int". "Posso solo presumere che sia stato uno dei cambiamenti silenziosi" - presumi erroneamente. I letterali carattere in C sono sempre stati di tipo int.
Jim Balter

0

Non lo so, ma immagino che sia stato più facile implementarlo in quel modo e non importava davvero. Non è stato fino a C ++ quando il tipo poteva determinare quale funzione sarebbe stata chiamata che doveva essere risolto.


0

Non lo sapevo davvero. Prima che esistessero i prototipi, qualsiasi cosa più stretta di un int veniva convertita in un int quando la si utilizzava come argomento di una funzione. Questo potrebbe essere parte della spiegazione.


1
Un'altra povera "risposta". La conversione automatica di charto intrenderebbe del tutto superfluo che le costanti dei caratteri siano int. Ciò che è rilevante è che il linguaggio tratta le costanti dei caratteri in modo diverso (dando loro un tipo diverso) dalle charvariabili, e ciò che serve è una spiegazione di tale differenza.
Jim Balter

Grazie per la spiegazione che hai fornito di seguito. Potresti voler descrivere la tua spiegazione in modo più completo in una risposta, a cui appartiene, può essere votata e facilmente visibile dai visitatori. Inoltre, non ho mai detto di avere una buona risposta qui. Quindi il tuo giudizio di valore non è di aiuto.
Blaisorblade

0

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

È una specie di punto accademico perché il linguaggio avrebbe comunque potuto 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.

(x86 wonks potrebbe notare che esiste ad esempio un addh nativo che aggiunge i registri short-wide in un unico passaggio, ma all'interno del core RISC questo si traduce in due passaggi: aggiungi i numeri, quindi estendi il segno, come una coppia add / extsh su il PowerPC)


1
Ancora un'altra risposta sbagliata. Il problema qui è perché i caratteri letterali e le charvariabili hanno tipi diversi. Le promozioni automatiche, che riflettono l'hardware, non sono rilevanti: in realtà sono anti-rilevanti, perché le charvariabili vengono promosse automaticamente, quindi non c'è motivo per cui i caratteri letterali non siano di tipo char. Il vero motivo sono i letterali multibyte, che ora sono obsoleti.
Jim Balter

@ Jim Balter I letterali multibyte non sono affatto obsoleti; ci sono caratteri Unicode e UTF multibyte.
Crashworks

@Crashworks Stiamo parlando di letterali di caratteri multibyte , non di stringhe di caratteri multibyte . Cerca di prestare attenzione.
Jim Balter

4
Chrashworks ha scritto personaggi . Avresti dovuto scrivere che i caratteri letterali larghi (diciamo L'à ') richiedono più byte ma non sono chiamati letterali caratteri multibyte. Essere meno arrogante ti aiuterebbe a essere più preciso te stesso.
Blaisorblade

I caratteri letterali di @ Blaisorblade Wide non sono rilevanti qui - non hanno nulla a che fare con ciò che ho scritto. Sono stato preciso e ti manca la comprensione e il tuo falso tentativo di correggermi è ciò che è arrogante.
Jim Balter
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.