2012-04-04 14 views
13

Estoy aprendiendo el lenguaje C en Linux ahora y he encontrado una situación un poco extraña.Unicode almacenado en C char

Por lo que yo sé, el tipo de datos estándar de C char es ASCII, 1 byte (8 bits). Debería significar que solo puede contener caracteres ASCII.

En mi programa que utilizo char input[], que se llena por getchar función como esta pseudocódigo:

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

Lo extraño es que funciona no sólo para los caracteres ASCII, pero para cualquier personaje que se imaginan, tales como @&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čž en la entrada.

Mi pregunta es: ¿cómo es posible? Parece ser una de las muchas excepciones hermosas en C, pero realmente agradecería una explicación. ¿Es una cuestión de OS, compilador, súper característica adicional del lenguaje oculto?

Gracias.

+2

No son realmente los caracteres, son los bytes que se obtienen con 'getchar()'. Cada personaje está codificado como una secuencia de bytes. –

+1

Estos son caracteres relativamente normales. Intente ampliar su imaginación para incluir, por ejemplo, algunas letras chinas o japonesas. O prueba cirílico para un cambio :) Aquí está "Hola" en ruso para ti: "Привет". – dasblinkenlight

+0

@DanielFischer Entiendo que 'getchar()' lo decodifica en bytes (s). Pero ya no entiendo cómo se pueden mantener los bytes en el tipo de datos 'char', que debe ser _one_ byte. –

Respuesta

18

Aquí no hay magia: el lenguaje C le da acceso a los bytes sin procesar, ya que están almacenados en la memoria del comptuer. Si su terminal está utilizando utf-8 (lo que es probable), los caracteres no ASCII toman más de un byte en la memoria. Cuando vuelva a mostrar, nuestro código de terminal convierte estas secuencias en un solo carácter visualizado.

Simplemente cambie su código para imprimir el strlen de las cuerdas, y verá lo que quiero decir.

Para manejar correctamente los caracteres no ASCII utf-8 en C, debe usar alguna biblioteca para manejarlos, como glib, qt, o muchos otros.

+1

o intente imprimir solo la entrada [0] para ver que no imprimirá el primer carácter, sino solo el primer byte que probablemente sea un carácter no imprimible, y luego intente imprimir la entrada [0] y la entrada [1] juntas para ver el caracter multibyte – abresas

+0

Ok, acabo de probar algunas modificaciones de código y funciona exactamente como se describe. Gracias. Solo una nota sobre caracteres anchos - '' no es suficiente para el manejo adecuado de caracteres anchos? –

3

ASCII tiene 7 bits, no 8 bits. a char [] contiene bytes, que pueden estar en cualquier codificación - iso8859-1, utf-8, lo que usted desee. A C no le importa

2

Hay un tipo de datos wint_t (#include <wchar.h>) para caracteres no ASCII. Puede usar el método getwchar() para leerlos.

14

ASCII es un juego de caracteres de 7 bits. En C normalmente representado por un char de 8 bits. Si se establece el bit más alto en un byte de 8 bits, es no un carácter ASCII.

También tenga en cuenta que usted es no garantizado ASCII como base, aunque muchos ignoran otras situaciones. Si desea comprobar si un "primitivo" bytes es un carácter alfabético se puede en otras palabras no, al tomar atención a todos los sistemas, por ejemplo:

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

En su lugar, tendrá que usar ctype.h y decir :

isalpha(c); 

única excepción, que yo sepa, es para los números, en la mayoría de las mesas por lo menos, tienen valores contiguos.

Por lo tanto, esto funciona;

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

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

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

Pero esto no se garantiza que sea 'a':

alhpa_a = 0x61; 

sistemas no basados ​​en ASCII, es decir, usando EBCDIC; C en una plataforma de este tipo todavía funciona bien, pero aquí usan (principalmente) 8 bits en lugar de 7 y, por lo tanto, A se pueden codificar como 193 decimal y no como 65 como en ASCII.


Para ASCII sin embargo; los bytes que tienen el decimal 128 - 255, (8 bits en uso), se extienden y no forman parte del conjunto ASCII. Es decir. ISO-8859 usa este rango.

Lo que se hace a menudo; también es combinar dos o más bytes para un personaje. Entonces, si imprime dos bytes uno detrás del otro que se define como decir, utf80xc3 0x98 == Ø, obtendrá este carácter.

nuevo, esto depende qué entorno que se encuentra. En muchos valores ASCII sistemas de impresión/ambientes dan mismo resultado a través de juegos de caracteres, sistemas, etc. Pero la impresión bytes> 127 caracteres o dobles byted da un resultado diferente dependiendo de la configuración local.

Es decir:

Sr. A se ejecuta el programa se

Jasn €

Mientras que el Sr. B consigue

Jasπß

Ésta es tal vez especialmente relevantes para el Serie ISO-8859 y Windows-1252 de representación de un solo byte de caracteres extendidos, etc.


  • UTF-8#Codepage_layout, en UTF-8 tiene ASCII, entonces usted tiene secuencias especiales de las despedidas.
    • Cada secuencia comienza con un byte> 127 (que es el último byte ASCII),
    • seguido por un número dado de bytes que todo comienza con los bits de 10.
    • En otras palabras, nunca encontrará un byte ASCII en una representación UTF-8 de varios bytes.

Eso es; el primer byte en UTF-8, si no es ASCII, indica cuántos bytes tiene este personaje. También podría decir que los caracteres ASCII dicen que no hay más bytes, porque el bit más alto es 0.

es decir si el archivo interpretarse como 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 
... 

Como un ejemplo. Si miramos a uno de los personajes que mencionas. Si está en una terminal UTF-8:

$ echo -n "č" | xxd

debe ceder:

0000000: c48d ..

En otras palabras "C" está representado por la dos bytes 0xC4 y 0x8d. Agregue -b al comando xxd y obtenemos la representación binaria de los bytes. Les diseccionar la siguiente manera:

___ 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 

Esto nos da: 00100001101 2 = 269 = 0x10D => Uncode punto de código U + 010D == "C".

Este número también se puede utilizar en HTML como &#269; == č

común para este y muchos otros sistemas de código es que un byte de 8 bits es la base.


A menudo también es una cuestión de contexto. Como ejemplo tome GSM SMS, con ETSI GSM 03.38/03.40 (3GPP TS 23.038, 3GPP 23038). Allí también encontramos una tabla de caracteres de 7 bits, un alfabeto predeterminado GSM de 7 bits, pero en lugar de almacenarlos como 8 bits, se almacenan como 7 bits . De esta forma puede empacar más caracteres en un número dado de bytes. Es decir, el SMS estándar de 160 caracteres se convierte en 1280 bits o 160 bytes en ASCII y 1120 o 140 bytes en SMS.

1 No sin excepción, (es más a la historia).

I.e. un ejemplo simple de bytes guardados como septeto (7 bits) C8329BFD06 en formato SMS UDP a 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 

y 9 bytes "desempaquetado" se convierte en 10 caracteres.

+0

¡Este artículo es simplemente genial! Gracias por el resumen y la descripción general. –

+0

@Mimars; Se hizo un poco largo, pero, :). Es un tema interesante y resulta divertido ver cómo se han resuelto las cosas. También piense que es educativo, ya que uno puede usar una lógica similar al codificar, también cosas completamente diferentes. También hay algunas bellezas con ASCII y cómo todo está organizado y ordenado, es decir: pp3 aquí http://faculty.kfupm.edu.sa/ics/said/ics232Lectures/L11_LogicInstructions.doc. - También es educativo ver, por ejemplo, /usr/include/ctype.h, etc. – Morpfh

1

Esta es la magia de UTF-8, que ni siquiera tiene que preocuparse por cómo funciona. El único problema es que el tipo de datos C se llama char (para carácter), mientras que lo que realmente significa es byte. no hay correspondencia 1: 1 entre los caracteres y los bytes que los codifican.

¿Qué ocurre en su código es que, desde el punto de vista del programa, de entrada una secuencia de bytes , almacena los bytes en la memoria y si se imprime el texto se imprime bytes.A este código no le importa cómo estos bytes codifican los caracteres, solo es el terminal el que debe preocuparse por codificarlos en la entrada e interpretarlos correctamente en la salida.

1

Por supuesto, hay muchas bibliotecas que hace el trabajo, pero para decodificar rápidamente cualquier Unicode UTF-8, esta pequeña función es práctica:

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; 
} 

Teniendo en cuenta su código; puede iterar la cadena y leer los valores 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; 
    } 
}