Problemi di codifica dei caratteri in C ++ Visual Studio

Non essere in grado di avvolgere la mia mente intorno a questo è una vera fonte di vergogna …

Sto lavorando con una versione francese di Visual Studio (2008), in un francese Windows (XP). Gli accenti francesi messi nelle stringhe inviate alla finestra di output vengono corrotti. Idem in ingresso dalla finestra di output. Tipico problema di codifica dei caratteri, inserisco ANSI, ottengo UTF-8 in cambio, o qualcosa del genere. Quale impostazione può garantire che i caratteri rimangano in ANSI quando si mostra una stringa “hardcoded” alla finestra di output?

MODIFICARE:

Esempio:

#include  int main() { std:: cout << "àéêù" << std:: endl; return 0; } 

Mostrerà nell’output:

Ouu

(qui codificato come HTML per il tuo piacere di visione)

Mi piacerebbe davvero che mostrasse:

AEEU

Prima di andare oltre, dovrei menzionare che quello che stai facendo non è conforms a c / c ++. La specifica afferma in 2.2 quali set di caratteri sono validi nel codice sorgente. Non è molto in là, e tutti i personaggi usati sono in ascii. Quindi … Tutto quanto segue riguarda un’implementazione specifica (come succede, VC2008 su un computer locale statunitense).

Per cominciare, hai 4 caratteri sulla tua linea di cout e 4 glifi sull’output. Quindi il problema non è quello della codifica UTF8, poiché combinerebbe più caratteri di origine con meno glifi.

Dalla tua stringa sorgente al display sulla console, tutte queste cose giocano un ruolo:

  1. In che cosa codifica il tuo file sorgente (cioè come il tuo file C ++ sarà visto dal compilatore)
  2. Cosa fa il tuo compilatore con una stringa letterale e quale sorgente di codifica comprende
  3. come il tuo << interpreta la stringa codificata che stai trasmettendo
  4. cosa si aspetta la codifica della console
  5. come la console traduce quell'output in un glifo font.

Adesso...

1 e 2 sono abbastanza facili. Sembra che il compilatore indovini in che formato si trova il file sorgente e lo decodifica nella sua rappresentazione interna. Genera il segmento di dati corrispondente letterale della stringa nella codepage corrente indipendentemente dalla codifica sorgente. Non sono riuscito a trovare dettagli / controlli espliciti su questo.

3 è ancora più facile. Ad eccezione dei codici di controllo, << passa solo i dati in basso per char *.

4 è controllato da SetConsoleOutputCP . Dovrebbe essere l'impostazione predefinita per la codepage predefinita del sistema. Puoi anche capire quale hai con GetConsoleOutputCP (l'input è controllato in modo diverso, tramite SetConsoleCP )

5 è divertente. Ho sbattuto la testa per capire perché non riuscivo a far apparire correttamente l'é, usando CP1252 (Europa occidentale, windows). Si scopre che il mio font di sistema non ha il glifo per quel personaggio, e usa utilmente il glifo della mia codepage standard (capitale Theta, lo stesso che otterrei se non chiamassi SetConsoleOutputCP). Per risolverlo, ho dovuto cambiare il font che uso sulle console in Lucida Console (un font di tipo true).

Alcune cose interessanti che ho imparato osservando questo:

  • la codifica della sorgente non ha importanza, purché il compilatore possa capirlo (in particolare, cambiandola in UTF8 non ha modificato il codice generato. La mia stringa "é" era ancora codificata con CP1252 come 233 0 )
  • VC sta selezionando una codepage per i letterali stringa che non sembra controllare.
  • controllare ciò che la console mostra è più doloroso di quello che mi aspettavo

Quindi cosa significa questo per te ? Ecco alcuni consigli:

  • non usare non-ascii in stringhe letterali. Usa le risorse, dove controlli la codifica.
  • assicurati di sapere quale codifica è prevista dalla tua console e che il tuo font ha i glifi per rappresentare i caratteri che invii.
  • se vuoi capire quale codifica viene usata nel tuo caso, ti consiglio di stampare il valore reale del personaggio come numero intero. char * a = "é"; std::cout << (unsigned int) (unsigned char) a[0] char * a = "é"; std::cout << (unsigned int) (unsigned char) a[0] mostra per me 233, che è la codifica in CP1252.

A proposito, se quello che hai ottenuto è "ÓÚÛ¨" piuttosto che ciò che hai incollato, sembra che i tuoi 4 byte siano interpretati da qualche parte come CP850 .

Prova questo:

 #include  #include  int main() { std::locale::global(std::locale("")); std::cout << "àéêù" << std::endl; return 0; } 

Ho provato questo codice:

 #include  #include  #include  int main() { std::wstringstream wss; wss << L"àéêù"; std::wstring s = wss.str(); const wchar_t* p = s.c_str(); std::wcout << ws.str() << std::endl; std::wofstream file("C:\\a.txt"); file << p << endl; return 0; } 

Il debugger ha mostrato che wss, s e p avevano tutti i valori attesi (es. "Àéêù"), così come il file di output. Tuttavia, ciò che è apparso nella console è stato óúÛ¨.

Il problema è quindi nella console di Visual Studio, non nel C ++. Usando l'eccellente risposta di Bahbar, ho aggiunto:

  SetConsoleOutputCP(1252); 

come prima riga e l'output della console appariva come dovrebbe.

Usare _setmode() funziona (fonte) ed è probabilmente meglio che modificare la codepage o impostare una localizzazione, dal momento che effettivamente renderà il tuo programma utilizza Unicode. Esempio:

 #include  #include  #include  int wmain() { _setmode(_fileno(stdout), _O_U16TEXT); std::wcout << L"àéêù" << std::endl; return 0; } 

All'interno di Visual Studio, assicurati di configurare il tuo progetto per Unicode (fai clic con il tasto destro del mouse su Progetto -> fai clic su Generale -> Set di caratteri = Usa set di caratteri Unicode ).

Utenti MinGW:

  1. Definire sia UNICODE che _UNICODE
  2. Aggiungi -finput-charset=iso-8859-1 alle opzioni del compilatore per -finput-charset=iso-8859-1 a questo errore: " conversione in set di caratteri di esecuzione: argomento non valido "
  3. Aggiungi -municode alle opzioni del linker per aggirare "un riferimento indefinito -municode WinMain @ 16 "( leggi altro ).

Perché mi è stato richiesto, farò qualche negromanzia. Le altre risposte sono state del 2009, ma questo articolo è emerso ancora da una ricerca che ho fatto nel 2018. La situazione oggi è molto diversa. Inoltre, la risposta accettata era incompleta anche nel 2009.

Il set di caratteri di origine

Ogni compilatore (incluso Microsoft Visual Studio 2008 e versioni successive, gcc, clang e icc) leggerà i file sorgente UTF-8 che iniziano con BOM senza problemi e clang non leggerà nulla tranne UTF-8, quindi UTF-8 con una BOM è il minimo comune denominatore per i file sorgente C e C ++.

Lo standard di linguaggio non dice quale carattere di origine imposta il compilatore deve supportare. Alcuni file sorgente reali vengono persino salvati in un set di caratteri incompatibile con ASCII. Microsoft Visual C ++ nel 2008 supportava file di origine UTF-8 con un segno di ordinamento dei byte, così come entrambe le forms di UTF-16. Senza un segno di ordinamento dei byte, si presuppone che il file sia stato codificato nella pagina di codice a 8 bit corrente, che era sempre un superset di ASCII.

I set di caratteri di esecuzione

Nel 2012, il compilatore ha aggiunto un CL.EXE /utf-8 a CL.EXE . Oggi supporta anche gli switch /source-charset e /execution-charset , nonché /validate-charset per rilevare se il tuo file non è effettivamente UTF-8. Questa pagina su MSDN ha un collegamento alla documentazione sul supporto Unicode per ogni versione di Visual C ++.

Le versioni correnti dello standard C ++ dicono che il compilatore deve avere sia un set di caratteri di esecuzione, che determina il valore numerico delle costanti di carattere come 'a' , sia un set di caratteri wide di esecuzione che determina il valore di costanti a caratteri larghi come L'é' .

Per un po ‘per l’avvocato linguistico, ci sono pochissimi requisiti nello standard per come questi devono essere codificati, eppure Visual C e C ++ riescono a infrangerli. Deve contenere circa 100 caratteri che non possono avere valori negativi e le codifiche delle cifre da '0' a '9' devono essere consecutive. Né il maiuscolo né le lettere minuscole devono essere, perché non erano su alcuni vecchi mainframe. (Cioè, '0'+9 deve essere uguale a '9' , ma c’è ancora un compilatore in uso nel mondo reale oggi il cui comportamento predefinito è che 'a'+9 non è 'j' ma '«' , e questo è legale.) Il set di esecuzione di caratteri ampi deve includere il set di esecuzione di base e avere abbastanza bit per contenere tutti i caratteri di qualsiasi locale supportato. Ogni compilatore mainstream supporta almeno un locale Unicode e comprende caratteri Unicode validi specificati con \Uxxxxxxxx , ma un compilatore che non ha potuto affermare di essere conforms allo standard.

Il modo in cui Visual C e C ++ violano lo standard del linguaggio è quello di rendere wchar_t UTF-16, che può rappresentare solo alcuni caratteri come coppie surrogate, quando lo standard dice che wchar_t deve essere una codifica a larghezza fissa. Questo perché Microsoft ha definito wchar_t come 16 bit di larghezza negli anni ’90, prima che il comitato Unicode capisse che 16 bit non sarebbero stati sufficienti per il mondo intero e Microsoft non avrebbe infranto le API di Windows. Supporta anche il tipo standard char32_t .

Letterali stringa UTF-8

Il terzo problema sollevato da questa domanda è come far sì che il compilatore codifichi una stringa letterale come UTF-8 in memoria. Sei stato in grado di scrivere qualcosa di simile dal C ++ 11:

 constexpr unsigned char hola_utf8[] = u8"¡Hola, mundo!"; 

Questo codificherà la stringa come la sua rappresentazione byte UTF-8 a terminazione nulla indipendentemente dal fatto che il set di caratteri sorgente sia UTF-8, UTF-16, Latin-1, CP1252 o anche IBM EBCDIC 1047 (che è un esempio sciocco teorico ma ancora, per compatibilità con le versioni precedenti, il default sul compilatore mainframe della serie Z di IBM). Cioè, è equivalente all’inizializzazione dell’array con { 0xC2, 0xA1, 'H', /* ... , */ '!', 0 } .

Se sarebbe troppo scomodo inserire un carattere o se si desidera distinguere tra caratteri superficialmente identici come spazio e spazio non interrotto o caratteri precomposti e combinati, si dispone anche di caratteri di escape universali:

 constexpr unsigned char hola_utf8[] = u8"\u00a1Hola, mundo!"; 

È ansible utilizzarli indipendentemente dal set di caratteri di origine e indipendentemente dal fatto che si stiano memorizzando i valori letterali come UTF-8, UTF-16 o UCS-4. Sono stati originariamente aggiunti in C99, ma Microsoft li ha supportati in Visual Studio 2015. C’è un altro modo per farlo che funzionava in Visual C o C ++ 2008, tuttavia: codici di escape ottali ed esadecimali. Avresti codificato i valori letterali UTF-8 in quella versione del compilatore con:

 const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!"; 
 //Save As Windows 1252 #include #include int main() { SetConsoleOutputCP(1252); std:: cout << "àéêù" << std:: endl; } 

Visual Studio non supporta UTF 8 per C ++, ma supporta parzialmente C:

 //Save As UTF8 without signature #include #include int main() { SetConsoleOutputCP(65001); printf("àéêù\n"); } 

Assicurati di non dimenticare di cambiare il font della console con Lucida Consolas come menzionato da Bahbar: è stato cruciale nel mio caso (vincere in Francia 7 64 bit con VC 2012).

Quindi, come menzionato da altri utenti, usa SetConsoleOutputCP (1252) per C ++ ma potrebbe non riuscire a seconda delle pagine disponibili, quindi potresti voler utilizzare GetConsoleOutputCP () per verificare che funzioni o almeno per verificare che SetConsoleOutputCP (1252) restituisca zero. Anche la modifica delle impostazioni internazionali globali funziona (per qualche ragione non è necessario fare cout.imbue (locale ()), ma potrebbe rompere alcune librairie!

In C , SetConsoleOutputCP (65001); o l’approccio basato sulle impostazioni locali ha funzionato per me una volta che avevo salvato il codice sorgente come UTF8 senza firma (scorrere verso il basso, la scelta della firma sans è molto più in basso nell’elenco delle pagine).

Input usando SetConsoleCP (65001); fallito per me apparentemente a causa di una ctriggers implementazione della pagina 65001 in Windows. L’approccio locale è fallito sia in C che in C ++. Una soluzione più coinvolgente, non basata su caratteri nativi ma su wchar_t sembra necessaria.