Programación en C: ¿Cómo programar para Unicode?


¿Qué requisitos previos se necesitan para hacer una programación Unicode estricta?

¿Implica esto que mi código no debe usar tipos char en ningún lugar y que se deben usar funciones que puedan tratar con wint_t y wchar_t?

¿Y cuál es el papel desempeñado por las secuencias de caracteres multibyte en este escenario?

Author: Jonathan Leffler, 2009-02-09

8 answers

Tenga en cuenta que esto no se trata de "estricta programación unicode" per se, sino de alguna experiencia práctica.

Lo que hicimos en mi empresa fue crear una biblioteca de envoltura alrededor de la biblioteca ICU de IBM. La librería wrapper tiene una interfaz UTF-8 y se convierte a UTF-16 cuando es necesario llamar a ICU. En nuestro caso, no nos preocupamos demasiado por los éxitos de rendimiento. Cuando el rendimiento era un problema, también suministramos interfaces UTF-16 (utilizando nuestro propio tipo de datos).

Las solicitudes podrían permanecer en gran medida tal cual (usando char), aunque en algunos casos necesitan ser conscientes de ciertos problemas. Por ejemplo, en lugar de strncpy() usamos un wrapper que evita cortar secuencias UTF-8. En nuestro caso, esto es suficiente, pero también se podrían considerar las comprobaciones para combinar caracteres. También tenemos envoltorios para contar el número de codepoints, el número de grafemas, etc.

Al interactuar con otros sistemas, a veces necesitamos hacer una composición de caracteres personalizada, por lo que puede necesitar algo de flexibilidad allí (dependiendo de su aplicación).

No usamos wchar_t. El uso de ICU evita problemas inesperados en la portabilidad (pero no otros problemas inesperados, por supuesto :-).

 20
Author: Hans van Eck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-08 22:44:06

C99 o anterior

El estándar C (C99) proporciona caracteres anchos y caracteres multi-byte, pero como no hay garantía sobre lo que esos caracteres anchos pueden contener, su valor es algo limitado. Para una implementación dada, proporcionan soporte útil, pero si su código debe poder moverse entre implementaciones, no hay garantía suficiente de que sean útiles.

En consecuencia, el enfoque sugerido por Hans van Eck (que es escribir una envoltura alrededor de la ICU-International Components for Unicode-library) está sound, IMO.

La codificación UTF-8 tiene muchos méritos, uno de los cuales es que si no interfiere con los datos (truncándolos, por ejemplo), entonces puede ser copiado por funciones que no son completamente conscientes de las complejidades de la codificación UTF-8. Esto no es categóricamente el caso con wchar_t.

Unicode en su totalidad es un formato de 21 bits. Es decir, Unicode reserva puntos de código de U + 0000 a U+10FFFF.

Uno de los lo útil de los formatos UTF-8, UTF-16 y UTF-32 (donde UTF significa Unicode Transformation Format - ver Unicode) es que puede convertir entre las tres representaciones sin pérdida de información. Cada uno puede representar cualquier cosa que los demás pueden representar. Tanto UTF-8 como UTF-16 son formatos multibyte.

UTF-8 es bien conocido por ser un formato multi-byte, con una estructura cuidadosa que hace posible encontrar el inicio de caracteres en una cadena de forma confiable, comenzando en cualquier punto en la cadena. Los caracteres de un solo byte tienen el bit alto establecido en cero. Los caracteres multi-byte tienen el primer carácter que comienza con uno de los patrones de bits 110, 1110 o 11110 (para caracteres de 2 bytes, 3 bytes o 4 bytes), con los bytes posteriores siempre comenzando 10. Los caracteres de continuación están siempre en el rango 0x80 .. 0xBF. Hay reglas de que los caracteres UTF-8 deben representarse en el formato mínimo posible. Una consecuencia de estas reglas es que los bytes 0xC0 y 0xC1 (también 0xF5..0xFF) no puede aparecer en datos UTF-8 válidos.

 U+0000 ..   U+007F  1 byte   0xxx xxxx
 U+0080 ..   U+07FF  2 bytes  110x xxxx   10xx xxxx
 U+0800 ..   U+FFFF  3 bytes  1110 xxxx   10xx xxxx   10xx xxxx
U+10000 .. U+10FFFF  4 bytes  1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx

Originalmente, se esperaba que Unicode fuera un conjunto de código de 16 bits y que todo encajara en un espacio de código de 16 bits. Desafortunadamente, el mundo real es más complejo, y tuvo que ser ampliado a la codificación actual de 21 bits.

UTF-16 es, por lo tanto, un código de unidad única (palabra de 16 bits) establecido para el 'Plano Multilingüe Básico', lo que significa que los caracteres con puntos de código Unicode U+0000 .. U + FFFF, pero utiliza dos unidades (32 bits) para caracteres fuera de este rango. Por lo tanto, el código que funciona con la codificación UTF-16 debe ser capaz de manejar codificaciones de ancho variable, al igual que UTF-8 debe. Los códigos para los caracteres de doble unidad se llaman sustitutos.

Los sustitutos son puntos de código de dos rangos especiales de valores Unicode, reservados para su uso como valores iniciales y finales de unidades de código emparejadas en UTF-16. Los sustitutos principales, también llamados altos, son de U + D800 a U + DBFF, y los sustitutos finales, o bajos, son de U + DC00 a U + DFFF. Se les llama sustitutos, ya que no representan a los personajes directamente, sino solo como una pareja.

UTF-32, por supuesto, puede codificar cualquier punto de código Unicode en una sola unidad de almacenamiento. Es eficiente para la computación, pero no para el almacenamiento.

Puede encontrar mucha más información en los sitios web ICU y Unicode.

C11 y <uchar.h>

El estándar C11 cambió las reglas, pero no todas las implementaciones han alcanzado cambios incluso ahora (mediados de 2017). El estándar C11 resume los cambios para el soporte Unicode como:

  • Caracteres Unicode y cadenas (<uchar.h>) (especificado originalmente en ISO/IEC TR 19769:2004)

Lo que sigue es un esquema mínimo de la funcionalidad. La especificación incluye:

6.4.3 Nombres universales de caracteres

Sintaxis
universal-character-name:
    \u hex-quad
    \U hex-quad hex-quad
hex-quad:
     dígito hexadecimal dígito hexadecimal hexadecimal-digit hexadecimal-digit

7.28 Utilidades Unicode <uchar.h>

El encabezado <uchar.h> declara tipos y funciones para manipular caracteres Unicode.

Los tipos declarados son mbstate_t (descritos en 7.29.1) y size_t (descritos en 7.19);

char16_t

Que es un entero sin signo tipo utilizado para caracteres de 16 bits y es el mismo tipo que uint_least16_t (descrito en 7.20.1.2); y

char32_t

Que es un tipo entero sin signo utilizado para caracteres de 32 bits y es el mismo tipo que uint_least32_t (también descrito en 7.20.1.2).

(Traduciendo las referencias cruzadas: <stddef.h> define size_t, <wchar.h> define mbstate_t, y <stdint.h> define uint_least16_t y uint_least32_t.) El encabezado <uchar.h> también define un conjunto mínimo de funciones de conversión (reiniciables):

  • mbrtoc16()
  • c16rtomb()
  • mbrtoc32()
  • c32rtomb()

Hay reglas sobre qué caracteres Unicode se pueden usar en identificadores usando las notaciones \unnnn o \U00nnnnnn. Es posible que tenga que activar activamente el soporte para dichos caracteres en los identificadores. Por ejemplo, GCC requiere -fextended-identifiers para permitir estos identificadores.

Tenga en cuenta que macOS Sierra (10.12.5), por nombrar solo una plataforma, no es compatible con <uchar.h>.

 36
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-11 02:26:22

Esta FAQ es una gran cantidad de información. Entre esa página y este artículo de Joel Spolsky, tendrás un buen comienzo.

Una conclusión a la que llegué en el camino:

  • wchar_t es 16 bits en Windows, pero no necesariamente 16 bits en otras plataformas. Creo que es un mal necesario en Windows, pero probablemente se puede evitar en otros lugares. La razón por la que es importante en Windows es que lo necesita para usar archivos que tienen caracteres no ASCII en el nombre (junto con la versión W de funciones).

  • Tenga en cuenta que las API de Windows que toman cadenas wchar_t esperan codificación UTF-16. Tenga en cuenta también que esto es diferente de UCS-2. Toma nota de las parejas sustitutas. Esta página de prueba tiene pruebas esclarecedoras.

  • Si estás programando en Windows, no puedes usar fopen(), fread(), fwrite(), etc. ya que solo toman char * y no entienden la codificación UTF-8. Hace que la portabilidad sea dolorosa.

 10
Author: dbyron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-22 13:34:39

Para hacer estricta programación Unicode:

  • Solo use API de cadena que sean compatibles con Unicode (NO strlen, strcpy, ... pero sus contrapartes extendidaswstrlen, wsstrcpy, ...)
  • Cuando se trata de un bloque de texto, utilice una codificación que permita almacenar caracteres Unicode (utf-7, utf-8, utf-16, ucs-2, ...) sin pérdida.
  • Compruebe que el conjunto de caracteres predeterminado del sistema operativo es compatible con Unicode (por ejemplo: utf-8)
  • Utilice fuentes compatibles con Unicode (p. ej. arial_unicode)

Multi-byte character sequences es una codificación anterior a la codificación UTF-16 (la que se usa normalmente con wchar_t) y me parece que es más bien solo para Windows.

Nunca he oído hablar de wint_t.

 7
Author: sebastien,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-27 01:07:50

Lo más importante es siempre hacer una clara distinción entre texto y datos binarios. Intente seguir el modelo de Python 3.x str vs bytes o SQL TEXT vs BLOB.

Desafortunadamente, C confunde el problema usando char tanto para "carácter ASCII" como para int_least8_t. Usted querrá hacer algo como:

typedef char UTF8; // for code units of UTF-8 strings
typedef unsigned char BYTE; // for binary data

Es posible que desee typedefs para unidades de código UTF-16 y UTF-32 también, pero esto es más complicado porque la codificación de wchar_t no está definida. Necesitarás solo un preprocesador #if s. Algunas macros útiles en C y C++0x son:

  • __STDC_UTF_16__ - Si está definido, el tipo _Char16_t existe y es UTF-16.
  • __STDC_UTF_32__ - Si está definido, el tipo _Char32_t existe y es UTF-32.
  • __STDC_ISO_10646__ - Si está definido, entonces wchar_t es UTF-32.
  • _WIN32 - En Windows, wchar_t es UTF-16, aunque esto rompe el estándar.
  • WCHAR_MAX - Se puede usar para determinar el tamaño de wchar_t, pero no si el sistema operativo lo usa para representar Unicode.

¿Implica esto que mi código debería no utilice tipos de carbón en cualquier lugar y que las funciones necesitan ser utilizadas que pueden lidiar con wint_t y wchar_t?

Véase también:

No. UTF-8 es una codificación Unicode perfectamente válida que utiliza cadenas char*. Tiene la ventaja de que si su programa es transparente a bytes no ASCII (por ejemplo, un convertidor de fin de línea que actúa sobre \r y \n pero pasa a través de otros caracteres sin cambios), ¡no necesitará hacer ningún cambio en absoluto!

Si va con UTF-8, tendrá que cambiar todas las suposiciones que char = carácter (por ejemplo, no llame a toupper en un bucle) o char = columna de pantalla (por ejemplo, para el ajuste de texto).

Si va con UTF-32, tendrá la simplicidad de caracteres de ancho fijo (pero no grafemas de ancho fijo , pero sí necesita cambiar el tipo de todas sus cadenas).

Si va con UTF-16, tendrá que descartar tanto la suposición de caracteres de ancho fijo y la suposición de unidades de código de 8 bits, lo que hace que esta sea la ruta de actualización más difícil desde las codificaciones de un solo byte.

Recomendaría activamente evitar wchar_t porque no es multiplataforma: A veces es UTF-32, a veces es UTF-16, y a veces es una codificación pre-Unicode de Asia Oriental. Yo recomendaría usando typedefs

, Aún más importante, evitar TCHAR.

 3
Author: dan04,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:33

Básicamente desea tratar con cadenas en memoria como matrices wchar_t en lugar de char. Cuando haces cualquier tipo de E/S (como leer/escribir archivos) puedes codificar/decodificar usando UTF-8 (esta es probablemente la codificación más común) que es bastante simple de implementar. Busca en Google los RFCs. Así que en memoria nada debe ser multi-byte. Un wchar_t representa un carácter. Sin embargo, cuando se llega a serializar, es cuando se necesita codificar a algo como UTF-8 donde se representan algunos caracteres por múltiples bytes.

También tendrás que escribir nuevas versiones de strcmp, etc. para las cadenas de caracteres anchas, pero esto no es un gran problema. El mayor problema será la interop con bibliotecas / código existente que solo acepta matrices char.

Y cuando se trata de sizeof(wchar_t) (necesitará 4 bytes si desea hacerlo bien) siempre puede redefinirlo a un tamaño más grande con typedef/macro hacks si lo necesita.

 2
Author: Mike Weller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-09 06:40:42

No confiaría en ninguna implementación estándar de bibliotecas. Simplemente rueda tus propios tipos unicode.

#include <windows.h>

typedef unsigned char utf8_t;
typedef unsigned short utf16_t;
typedef unsigned long utf32_t;

int main ( int argc, char *argv[] )
{
  int msgBoxId;
  utf16_t lpText[] = { 0x03B1, 0x0009, 0x03B2, 0x0009, 0x03B3, 0x0009, 0x03B4, 0x0000 };
  utf16_t lpCaption[] = L"Greek Characters";
  unsigned int uType = MB_OK;
  msgBoxId = MessageBoxW( NULL, lpText, lpCaption, uType );
  return 0;
}
 2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-29 18:45:44

Por lo que sé, wchar_t depende de la implementación (como se puede ver en este artículo wiki). Y no es unicode.

 1
Author: PolyThinker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-09 06:03:11