2012-04-04 10 views
13

Sto imparando il linguaggio C su Linux ora e mi sono imbattuto in una strana situazione.Unicode memorizzato in C char

Per quanto ne so, il tipo di dati C standard char è ASCII, 1 byte (8 bit). Dovrebbe significare che può contenere solo caratteri ASCII.

Nel mio programma che uso char input[], che viene riempito da getchar funzione come questa pseudocodice:

char input[20]; 
int z, i; 
for(i = 0; i < 20; i++) 
{ 
    z = getchar(); 
    input[i] = z; 
} 

La cosa strana è che funziona non solo per i caratteri ASCII, ma per qualsiasi carattere immagino, come ad esempio @&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čž sull'ingresso.

La mia domanda è: come è possibile? Sembra essere una delle tante belle eccezioni in C, ma apprezzerei molto la spiegazione. È una questione di OS, compilatore, super-funzione aggiuntiva del linguaggio nascosto?

Grazie.

+2

Non sono proprio i caratteri, sono i byte ottenuti con 'getchar()'. Ogni carattere è codificato come una sequenza di byte. –

+1

Questi sono caratteri relativamente normali. Prova ad ampliare la tua immaginazione per includere, ad esempio, alcune lettere cinesi o giapponesi. Oppure prova Cyrillic per un cambiamento :) Ecco "Ciao" in russo per te: "Привет". – dasblinkenlight

+0

@DanielFischer Capisco, che 'getchar()' lo decodifica in byte (s). Ma già non capisco, in che modo i byte possono essere conservati nel tipo di dati 'char', che dovrebbe essere _one_ byte. –

risposta

18

Qui non c'è magia - Il linguaggio C ti dà accesso ai byte grezzi, poiché sono memorizzati nella memoria del comptuer. Se il terminale utilizza utf-8 (che è probabile), i caratteri non ASCII richiedono più di un byte in memoria. Quando visualizzi di nuovo, è il nostro codice terminale che converte queste sequenze in un singolo carattere visualizzato.

Basta modificare il codice per stampare lo strlen delle stringhe e vedrete cosa intendo.

Per gestire correttamente i caratteri utf-8 non ASCII in C è necessario utilizzare alcune librerie per gestirli per te, come glib, qt o molti altri.

+1

o prova a stampare solo l'input [0] per vedere che non stamperà il primo carattere, ma solo il primo byte che molto probabilmente sarà un carattere non stampabile, quindi prova a stampare l'input [0] e l'input [1] insieme per vedere il carattere multibyte. – abresas

+0

Ok, ho appena provato alcune modifiche al codice e funziona esattamente come descritto. Grazie. Solo una nota sui caratteri ampi - '' non è sufficiente per una corretta gestione dei caratteri ampi? –

3

ASCII è 7 bit, non 8 bit. uno char [] contiene byte, che possono essere in qualsiasi codifica - iso8859-1, utf-8, qualunque cosa tu voglia. A C non importa.

2

C'è un tipo di dati wint_t (#include <wchar.h>) per caratteri non ASCII. È possibile utilizzare il metodo getwchar() per leggerli.

14

ASCII è un set di caratteri a 7 bit. In C normalmente rappresentato da un char a 8 bit. Se il bit più alto in un byte da 8 bit è impostato, è non un carattere ASCII.

Si noti inoltre che si è non garantito ASCII come base, ma molti ignorano altri scenari. Se si desidera controllare se un "primitivo" byte è un carattere alfabetico è possibile in altre parole non, durante l'assunzione di attenzione a tutti i sistemi, dire:

is_alpha = (c > 0x40 && c < 0x5b) || (c > 0x60 && c < 0x7b); 

Invece si dovrà utilizzare ctype.h e dire :

isalpha(c); 

Unica eccezione, per quanto ne so, è per i numeri, sulla maggior parte dei tavoli, almeno, hanno valori contigui.

Così funziona;

char ninec = '9'; 
char eightc = '8'; 

int nine = ninec - '0'; 
int eight = eightc - '0'; 

printf("%d\n", nine); 
printf("%d\n", eight); 

Ma questo non è garantito per essere 'a':

alhpa_a = 0x61; 

Systems non basata su ASCII, vale a dire utilizzando EBCDIC; C su una piattaforma di questo tipo funziona ancora bene ma qui (per lo più) usano 8 bit anziché 7 e cioè A può essere codificato come decimale 193 e non 65 come in ASCII.


Per ASCII tuttavia; byte con decimale 128 - 255, (8 bit in uso), è esteso e non parte del set ASCII. Cioè ISO-8859 utilizza questo intervallo.

Cosa viene spesso fatto; è anche quello di combinare due o più byte con un carattere. Quindi se si stampano due byte uno dopo l'altro definito come, utf80xc3 0x98 == Ø, si otterrà questo carattere.

Questo dipende ancora una volta su quale ambiente ci si trova. In molte valori ASCII di stampa sistemi/ambienti di dare lo stesso risultato attraverso i set di caratteri, sistemi ecc Ma la stampa byte> 127 o doppi caratteri byted dà un risultato diverso a seconda della configurazione locale.

Ie:

signor A esecuzione programma ottiene

JASN €

Mentre il signor B ottiene

Jasπß

Questo è forse particolarmente rilevante per la ISO-8859 series e Windows-1252 di rappresentazione a byte singolo di caratteri estesi, ecc.


  • UTF-8#Codepage_layout, in UTF-8 si dispone di ASCII, allora avete sequenze speciali di addii.
    • Ogni sequenza inizia con un byte> 127 (che è ultimo byte ASCII),
    • seguito da un certo numero di byte che tutti inizia con i bit 10.
    • In altre parole, non si troverà mai un byte ASCII in una rappresentazione UTF-8 multi-byte.

che è; il primo byte in UTF-8, se non ASCII, indica quanti byte ha questo personaggio. Si potrebbe anche dire che i caratteri ASCII dicono che non seguono più byte - perché il bit più alto è 0.

cioè se il file interpretata come UTF-8:

fgetc(c); 

if c < 128, 0x80, then ASCII 
if c == 194, 0xC2, then one more byte follow, interpret to symbol 
if c == 226, 0xE2, then two more byte follows, interpret to symbol 
... 

Come esempio. Se guardiamo uno dei personaggi che menzioni. Se in un terminale UTF-8:

$ echo -n "č" | xxd

dovrebbe produrre:

0000000: c48d ..

In altre parole "C" viene rappresentato dalle due bytes 0xC4 e 0x8D. Aggiungi -b al comando xxd e otteniamo la rappresentazione binaria dei byte. Noi li sezionare come segue:

___ byte 1 ___  ___ byte 2 ___      
|    | |    | 
0xc4 : 1100 0100 0x8d : 1000 1101 
     |     | 
     |     +-- all "follow" bytes starts with 10, rest: 00 1101 
     | 
     + 11 -> 2 bits set = two byte symbol, the "bits set" sequence 
       end with 0. (here 3 bits are used 110) : rest 0 0100 

Rest bits combined: xxx0 0100 xx00 1101 => 00100001101 
         \____/ \_____/ 
         |  | 
         |  +--- From last byte 
         +------------ From first byte 

Questo ci danno: 00100001101 = 269 = 0x10D => Uncode codepoint U + 010D == "C".

Questo numero può essere utilizzato anche in HTML come &#269; == č

comune per questo e un sacco di altri sistemi di codifica è che un byte di 8 bit è la base.


Spesso è anche una domanda sul contesto. Prendiamo ad esempio GSM SMS, con ETSI GSM 03.38/03.40 (3GPP TS 23.038, 3GPP 23038). Troviamo anche una tabella di caratteri a 7 bit, alfabeto predefinito GSM a 7 bit, ma invece di archiviarli come 8 bit vengono memorizzati come 7 bit . In questo modo puoi impacchettare più personaggi in un determinato numero di byte. Cioè SMS 160 caratteri standard diventa 1280 bit o 160 byte come ASCII e 1120 o 140 byte come SMS.

1 Non senza eccezione, (è più per la storia).

I.e. un semplice esempio di byte salvati come settetti (7 bit) C8329BFD06 in formato SMS UDP in ASCII:

       _________ 
7 bit UDP represented   |   +--- Alphas has same bits as ASCII 
as 8 bit hex     '0.......' 
C8329BFDBEBEE56C32    1100100 d * Prev last 6 bits + pp 1 
| | | | | | | | +- 00 110010 -> 1101100 l * Prev last 7 bits 
| | | | | | | +--- 0 1101100 -> 1110010 r * Prev 7 + 0 bits 
| | | | | | +----- 1110010 1 -> 1101111 o * Last 1 + prev 6 
| | | | | +------- 101111 10 -> 1010111 W * Last 2 + prev 5 
| | | | +--------- 10111 110 -> 1101111 o * Last 3 + prev 4 
| | | +----------- 1111 1101 -> 1101100 l * Last 4 + prev 3 
| | +------------- 100 11011 -> 1101100 l * Last 5 + prev 2 
| +--------------- 00 110010 -> 1100101 e * Last 6 + prev 1 
+----------------- 1 1001000 -> 1001000 H * Last 7 bits 
           '------' 
            | 
            +----- GSM Table as binary 

e 9 byte "spacchettato" diventa 10 caratteri.

+0

Questo articolo è semplicemente fantastico! Grazie per il riassunto e la panoramica. –

+0

@Mimars; È diventato un po 'lungo, ma, :). È un argomento interessante e trovo divertente vedere come sono state risolte le cose. Pensa anche che è educativo in quanto si può usare una logica simile durante la codifica - anche cose completamente diverse. Ci sono anche alcune bellezze con ASCII e come tutto è organizzato e ordinato - cioè: pp3 qui http://faculty.kfupm.edu.sa/ics/said/ics232Lectures/L11_LogicInstructions.doc. - È anche educativo guardare a /usr/include/ctype.h ecc. – Morpfh

1

Questa è la magia di UTF-8, che non devi nemmeno preoccuparti di come funziona. L'unico problema è che il tipo di dati C è denominato char (per carattere), mentre quello che in realtà significa è byte. non esiste una corrispondenza 1: 1 tra i caratteri e i byte che li codificano.

Ciò che accade nel codice è che, dal punto di vista del programma, si immette una sequenza di byte, memorizza i byte in memoria e se si stampa il testo vengono stampati byte.Questo codice non interessa come questi byte codificano i caratteri, è solo il terminale che deve preoccuparsi di codificarli sugli input e interpretarli correttamente in output.

1

ci sono naturalmente molte biblioteche che fa il lavoro, ma per decodificare rapidamente qualsiasi Unicode UTF-8, questa piccola funzione è a portata di mano:

typedef unsigned char utf8_t; 

#define isunicode(c) (((c)&0xc0)==0xc0) 

int utf8_decode(const char *str,int *i) { 
    const utf8_t *s = (const utf8_t *)str; // Use unsigned chars 
    int u = *s,l = 1; 
    if(isunicode(u)) { 
     int a = (u&0x20)? ((u&0x10)? ((u&0x08)? ((u&0x04)? 6 : 5) : 4) : 3) : 2; 
     if(a<6 || !(u&0x02)) { 
      int b,p = 0; 
      u = ((u<<(a+1))&0xff)>>(a+1); 
      for(b=1; b<a; ++b) 
       u = (u<<6)|(s[l++]&0x3f); 
     } 
    } 
    if(i) *i += l; 
    return u; 
} 

Considerando il codice; è possibile scorrere la stringa e leggere i valori Unicode:

int l; 
for(i=0; i<20 && input[i]!='\0';) { 
    if(!isunicode(input[i])) i++; 
    else { 
     l = 0; 
     z = utf8_decode(&input[i],&l); 
     printf("Unicode value at %d is U+%04X and it\'s %d bytes.\n",i,z,l); 
     i += l; 
    } 
}