2015-05-22 16 views
5

La mia configurazione: gcc-4.9.2, ambiente UTF-8.Come utilizzare UTF-8 nel codice C?

Il seguente programma C funziona in ASCII, ma non in UTF-8.

Crea file di input:

echo -n 'привет мир' > /tmp/вход 

Questo è test.c:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define SIZE 10 

int main(void) 
{ 
    char buf[SIZE+1]; 
    char *pat = "привет мир"; 
    char str[SIZE+2]; 

    FILE *f1; 
    FILE *f2; 

    f1 = fopen("/tmp/вход","r"); 
    f2 = fopen("/tmp/выход","w"); 

    if (fread(buf, 1, SIZE, f1) > 0) { 
    buf[SIZE] = 0; 

    if (strncmp(buf, pat, SIZE) == 0) { 
     sprintf(str, "% 11s\n", buf); 
     fwrite(str, 1, SIZE+2, f2); 
    } 
    } 

    fclose(f1); 
    fclose(f2); 

    exit(0); 
} 

controllare il risultato:

./test; grep -q ' привет мир' /tmp/выход && echo OK 

Cosa si deve fare per far funzionare UTF-8 codice come se fosse un codice ASCII - non preoccuparsi di quanti byte un simbolo prende, ecc. In altre parole: cosa cambiare nell'esempio per trattare un y Simbolo UTF-8 come singola unità (che include argv, STDIN, STDOUT, STDERR, input di file, output e il codice del programma)?

+0

Check link http://www.nubaria.com/en/blog/?p=289 – Razib

+1

vostro 'modello grep' ha uno spazio iniziale . – tripleee

+6

Inoltre, non dare un nome al 'test' dei programmi, poiché questo è un built-in di shell. ('./test' funziona, naturalmente, ma è un'abitudine che non vuoi mantenere.) – tripleee

risposta

6
#define SIZE 10 

La dimensione del buffer di 10 è sufficiente per memorizzare la stringa UTF-8 привет мир. Prova a cambiarlo a un valore più grande. Sul mio sistema (Ubuntu 12.04, gcc 4.8.1), cambiandolo a 20, funzionava perfettamente.

UTF-8 è una codifica multibyte che utilizza da 1 a 4 byte per carattere. Quindi, è più sicuro usare 40 come dimensione del buffer sopra. C'è una grande discussione allo How many bytes does one Unicode character take? che potrebbe essere interessante.

+0

La modifica di SIZE a 20 non funziona - OK non è stampato (vedi 'echo OK' check nella mia domanda). –

+2

Devi fare più lavoro che cambiare SIZE a 20, ma questo è un passaggio chiave nel processo. –

5

Siddhartha Ghosh 's answer fornisce il problema di base. La correzione del codice richiede più lavoro, però.

ho usato il seguente script (chk-utf8-test.sh):

echo -n 'привет мир' > вход 
make utf8-test 
./utf8-test 
grep -q 'привет мир' выход && echo OK 

Ho chiamato il vostro programma utf8-test.c e modificato la fonte come questo, eliminando i riferimenti a /tmp, e di essere più attenti con le lunghezze:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define SIZE 40 

int main(void) 
{ 
    char buf[SIZE + 1]; 
    char *pat = "привет мир"; 
    char str[SIZE + 2]; 

    FILE *f1 = fopen("вход", "r"); 
    FILE *f2 = fopen("выход", "w"); 

    if (f1 == 0 || f2 == 0) 
    { 
     fprintf(stderr, "Failed to open one or both files\n"); 
     return(1); 
    } 

    size_t nbytes; 
    if ((nbytes = fread(buf, 1, SIZE, f1)) > 0) 
    { 
     buf[nbytes] = 0; 

     if (strncmp(buf, pat, nbytes) == 0) 
     { 
      sprintf(str, "%.*s\n", (int)nbytes, buf); 
      fwrite(str, 1, nbytes, f2); 
     } 
    } 

    fclose(f1); 
    fclose(f2); 

    return(0); 
} 

E quando ho eseguito lo script, ho ottenuto:

$ bash -x chk-utf8-test.sh 
+ '[' -f /etc/bashrc ']' 
+ . /etc/bashrc 
++ '[' -z '' ']' 
++ return 
+ alias 'r=fc -e -' 
+ echo -n 'привет мир' 
+ make utf8-test 
gcc -O3 -g -std=c11 -Wall -Wextra -Werror utf8-test.c -o utf8-test 
+ ./utf8-test 
+ grep -q 'привет мир' $'в?\213?\205од' 
+ echo OK 
OK 
$ 

Per la cronaca, stavo usando GCC 5.1.0 su Mac OS X 10.10.3.

+0

Hai dimenticato '% 11s' in sprintf e lo spazio iniziale in grep. Tuttavia, 'OK' non viene stampato. –

+0

Oh, ho dimenticato di menzionare che il mio compilatore obietta nello spazio. (Cosa fa per te - il messaggio menzionato 'gnu_printf'? Una flag di spazio è rilevante per le conversioni numeriche, ma non per le conversioni di stringhe). Se volevo uno spazio all'inizio, va prima di '%'. E non ho dimenticato l'11; Ho cambiato '11' in'. * 'E ho passato il numero corretto di byte come argomento' int' su 'printf()'. Non stai usando caratteri ampi; stai usando le stringhe di byte, e i caratteri UTF-8 sono di larghezza variabile, anche se a parte lo spazio, i tuoi sono tutti 2 byte di lunghezza in UTF-8. Devi lavorare con i byte. –

+0

Il messaggio di errore esatto che ho ricevuto è stato: 'utf8-test.c: 23: 20: errore: '' flag utilizzato con '% s' formato gnu_printf [-Werror = format =]'. La risposta breve è che non è possibile utilizzare UTF-8 come ASCII utilizzando le tecniche che si sta tentando di utilizzare. Se si utilizzano variabili e funzioni di caratteri estese, si ha una possibilità, ma non se si sta usando 'char' e le funzioni basate su byte. –

3

Questo è più di un corollario alle altre risposte, ma cercherò di spiegarlo da un'angolazione leggermente diversa.

Ecco la versione del codice di Jonathan Leffler, con tre piccole modifiche: (1) Ho reso espliciti i singoli byte effettivi nelle stringhe UTF-8; e (2) Ho modificato lo specificatore della larghezza della stringa di formattazione sprintf per sperabilmente fare ciò che si sta tentando di fare. Anche tangenzialmente (3) Ho usato perror per ottenere un messaggio di errore leggermente più utile quando qualcosa non funziona.

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define SIZE 40 

int main(void) 
{ 
    char buf[SIZE + 1]; 
    char *pat = "\320\277\321\200\320\270\320\262\320\265\321\202" 
    " \320\274\320\270\321\200"; /* "привет мир" */ 
    char str[SIZE + 2]; 

    FILE *f1 = fopen("\320\262\321\205\320\276\320\264", "r"); /* "вход" */ 
    FILE *f2 = fopen("\320\262\321\213\321\205\320\276\320\264", "w"); /* "выход" */ 

    if (f1 == 0 || f2 == 0) 
    { 
     perror("Failed to open one or both files"); /* use perror() */ 
     return(1); 
    } 

    size_t nbytes; 
    if ((nbytes = fread(buf, 1, SIZE, f1)) > 0) 
    { 
     buf[nbytes] = 0; 

     if (strncmp(buf, pat, nbytes) == 0) 
     { 
      sprintf(str, "%*s\n", 1+(int)nbytes, buf); /* nbytes+1 length specifier */ 
      fwrite(str, 1, 1+nbytes, f2); /* +1 here too */ 
     } 
    } 

    fclose(f1); 
    fclose(f2); 

    return(0); 
} 

Il comportamento di sprintf con una larghezza identificatore numerico positivo è di rilievo con spazi da sinistra, quindi lo spazio si è tentato di utilizzare è superfluo.Ma devi assicurarti che il campo obiettivo sia più largo della stringa che stai stampando per far sì che qualsiasi padding abbia effettivamente luogo.

Giusto per rendere autonoma questa risposta, ripeterò ciò che altri hanno già detto. Un tradizionale char è sempre esattamente un byte, ma un carattere in UTF-8 di solito non è esattamente un byte, tranne quando tutti i tuoi caratteri sono in realtà ASCII. Una delle attrattive di UTF-8 è che il codice C legacy non ha bisogno di sapere nulla di UTF-8 per poter continuare a lavorare, ma ovviamente l'ipotesi che un solo carattere è un glifo non può essere mantenuta. (Come si può vedere, per esempio, il glifo п nelle mappe "привет мир" per i due byte - e, di conseguenza, due char s -. "\320\277")

Questo è chiaramente meno che ideale, ma dimostra che tu puoi trattare UTF-8 come "solo byte" se il tuo codice non si preoccupa particolarmente della semantica degli glifi. Se il tuo lo fa, è meglio passare a wchar_t come descritto ad es. qui: http://www.gnu.org/software/libc/manual/html_node/Extended-Char-Intro.html

Tuttavia, lo standard wchar_t non è ideale quando l'aspettativa standard è UTF-8. Vedi per es. il GNU libunistring documentation per un'alternativa meno invadente e un po 'di background. Con questo, dovresti essere in grado di sostituire char con uint8_t e le varie funzioni str* con sostituzioni u8_str* ed essere fatto. L'ipotesi che un glifo sia uguale a un byte dovrà ancora essere affrontata, ma ciò diventa un aspetto tecnico minore nel tuo programma di esempio. Un adattamento è disponibile allo http://ideone.com/p0VfXq (anche se sfortunatamente la libreria non è disponibile su http://ideone.com/ quindi non può essere dimostrata lì).

+0

In realtà, stavo chiedendo come usare il normale UTF-8 nel mio programma, cioè come ottenere in 'C' l'equivalente di' perl -CSDA -Mutf8' Il tuo esempio non affronta la mia domanda, sebbene il link che hai fornito è decisamente sull'argomento. –

+0

Aggiunto un altro breve paragrafo su un'alternativa a 'wchar_t'. – tripleee

0

Probabilmente il file test.c non è memorizzato nel formato UTF-8 e per questo motivo "stringa" è ASCII e il confronto non è riuscito. Cambia la codifica del testo del file sorgente e riprova.

1

Il seguente codice funziona come richiesto:

#include <stdio.h> 
#include <locale.h> 
#include <stdlib.h> 
#include <wchar.h> 

#define SIZE 10 

int main(void) 
{ 
    setlocale(LC_ALL, ""); 
    wchar_t buf[SIZE+1]; 
    wchar_t *pat = L"привет мир"; 
    wchar_t str[SIZE+2]; 

    FILE *f1; 
    FILE *f2; 

    f1 = fopen("/tmp/вход","r"); 
    f2 = fopen("/tmp/выход","w"); 

    fgetws(buf, SIZE+1, f1); 

    if (wcsncmp(buf, pat, SIZE) == 0) { 
    swprintf(str, SIZE+2, L"% 11ls", buf); 
    fputws(str, f2); 
    } 

    fclose(f1); 
    fclose(f2); 

    exit(0); 
} 
Problemi correlati