Devo trasmettere al char unsigned prima di chiamare toupper?

Qualche tempo fa, qualcuno con alta reputazione qui su StackOverflow ha scritto in un commento che è necessario eseguire il cast di un char su unsigned char prima di chiamare std::toupper (e funzioni simili).

D’altra parte, Bjarne Stroustrup non menziona la necessità di farlo nel linguaggio di programmazione C ++. Usa solo toupper come

 string name = "Niels Stroustrup"; void m3() { string s = name.substr(6,10); // s = "Stroustr up" name.replace(0,5,"nicholas"); // name becomes "nicholas Stroustrup" name[0] = toupper(name[0]); // name becomes "Nicholas Stroustrup" } 

(Citato da detto libro, 4a edizione.)

Il riferimento dice che l’input deve essere rappresentabile come unsigned char . Per me sembra che valga per ogni char dato che i char e unsigned char hanno le stesse dimensioni.

Quindi questo cast non è necessario o Stroustrup è stato negligente?

Modifica: il manuale libstdc ++ menziona che il carattere di input deve provenire dal set di caratteri di origine di base , ma non trasmesso. Immagino che questo sia coperto dalla risposta di @Keith Thompson, hanno tutti una rappresentazione positiva come signed char e unsigned char ?

Sì, l’argomento toupper deve essere convertito in unsigned char per evitare il rischio di comportamento non definito.

I tipi char , signed char e unsigned char sono tre tipi distinti. char ha lo stesso intervallo e la stessa rappresentazione di un signed char unsigned char o di un unsigned char . (Il char normale è molto comune e può rappresentare valori nell’intervallo -128 .. + 127.)

La funzione toupper accetta un argomento int e restituisce un risultato int . Citando lo standard C, sezione 7.4 paragrafo 1:

In tutti i casi l’argomento è un int , il cui valore deve essere rappresentabile come un unsigned char o uguale al valore della macro EOF . Se l’argomento ha altri valori, il comportamento non è definito.

(C ++ incorpora la maggior parte della libreria standard C e rinvia la sua definizione allo standard C.)

L’operatore di indicizzazione [] su std::string restituisce un valore char . Se plain char è un tipo firmato e se il valore restituito dal name[0] sembra essere negativo, quindi l’espressione

 toupper(name[0]) 

ha un comportamento indefinito.

La lingua garantisce che, anche se è stato firmato un char semplice, tutti i membri del set di caratteri di base hanno valori non negativi, quindi, data l’inizializzazione

 string name = "Niels Stroustrup"; 

il programma non rischia un comportamento indefinito. Ma sì, in generale un valore char passato a toupper (o ad una qualsiasi delle funzioni dichiarate in / deve essere convertito in unsigned char , in modo che la conversione implicita in int non produca un negativo valore e causa un comportamento indefinito.

Le funzioni sono comunemente implementate utilizzando una tabella di ricerca. Qualcosa di simile a:

 // assume plain char is signed char c = -2; c = toupper(c); // undefined behavior 

può indicizzare fuori dai limiti di quella tabella.

Si noti che la conversione in unsigned :

 char c = -2; c = toupper((unsigned)c); // undefined behavior 

non evita il problema Se int è 32 bit, convertire il valore char -2 in unsigned yield 4294967294 . Questo è quindi implicitamente convertito in int (il tipo di parametro), che produce probabilmente -2 .

toupper può essere implementato in modo che si comporti in modo CHAR_MIN per i valori negativi (accettando tutti i valori da CHAR_MIN a UCHAR_MAX ), ma non è obbligatorio farlo. Inoltre, le funzioni in sono richieste per accettare un argomento con il valore EOF , che è tipicamente -1 .

Lo standard C ++ apporta modifiche ad alcune funzioni della libreria standard C. Ad esempio, strchr e molte altre funzioni sono sostituite da versioni sovraccaricate che applicano la correttezza const . Non ci sono tali regolazioni per le funzioni dichiarate in .

In C, toupper (e molte altre funzioni) prendono int s anche se ti aspetteresti che prendessero i char . Inoltre, char è firmato su alcune piattaforms e non firmato su altri.

Il consiglio di eseguire il cast su unsigned char prima di chiamare toupper è corretto per C. Non penso sia necessario in C ++, a patto che tu passi un int che è nel range. Non riesco a trovare nulla di specifico se è necessario in C ++.

Se si desidera eliminare il problema, utilizzare il toupper definito in . È un modello e accetta qualsiasi tipo di carattere accettabile. Devi anche passarlo a std::locale . Se non hai idea di quale locale scegliere, usa std::locale("") , che dovrebbe essere la locale preferita dell’utente:

 #include  #include  #include  #include  #include  int main() { std::string name("Bjarne Stroustrup"); std::string uppercase; std::locale loc(""); std::transform(name.begin(), name.end(), std::back_inserter(uppercase), [&loc](char c) { return std::toupper(c, loc); }); std::cout << name << '\n' << uppercase << '\n'; return 0; } 

Il riferimento si riferisce al valore che è rappresentabile come un unsigned char , non essendo un unsigned char . In altre UCHAR_MAX , il comportamento non è definito se il valore effettivo non è compreso tra 0 e UCHAR_MAX (in genere 255). (O EOF , che è fondamentalmente la ragione per cui prende un int invece di un char .)

Purtroppo Stroustrup è stato negligente 🙁
E sì, i codici delle lettere latine dovrebbero essere non negativi (e non sono richiesti cast) …
Alcune implementazioni funzionano correttamente senza eseguire il cast di unsigned char …
Secondo l’esperienza, potrebbe essere necessario attendere diverse ore per trovare la causa del segfault di un tale toupper (quando è noto che un segfault è presente) …
E ci sono anche isupper, islower ecc

Invece di trasmettere l’argomento come carattere non firmato, puoi eseguire il cast della funzione. Dovrai includere un’intestazione funzionale . Ecco un codice di esempio:

 #include  #include  #include  #include  #include  int main() { typedef unsigned char BYTE; // just in case std::string name("Daniel Brühl"); // used this name for its non-ascii character! std::transform(name.begin(), name.end(), name.begin(), (std::function)::toupper); std::cout << "uppercase name: " << name << '\n'; return 0; } 

L'output è:

 uppercase name: DANIEL BRüHL 

Come previsto, toupper non ha alcun effetto sui caratteri non ascii. Ma questo casting è utile per evitare comportamenti inaspettati.