Progtwigción en C: ¿Cómo progtwigr para Unicode?

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

¿Esto implica que mi código no debe usar tipos de caracteres en ninguna parte y que se deben usar funciones que puedan manejar wint_t y wchar_t ?

¿Y cuál es el papel que juegan las secuencias de caracteres multibyte en este escenario?

Tenga en cuenta que esto no se trata de “progtwigción Unicode estricta” per se, sino de alguna experiencia práctica.

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

Las aplicaciones podrían permanecer en gran medida tal como están (usando caracteres), aunque en algunos casos deben estar al tanto de ciertos problemas. Por ejemplo, en lugar de strncpy () usamos un contenedor que evita cortar secuencias UTF-8. En nuestro caso, esto es suficiente, pero también se podrían considerar controles para combinar caracteres. También tenemos envoltorios para contar el número de puntos de código, 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 cierta flexibilidad allí (según su aplicación).

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

C99 o anterior

El estándar C (C99) proporciona caracteres anchos y multibyte, pero como no hay garantía sobre qué pueden contener esos caracteres anchos, 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 un envoltorio alrededor de la ICU – Componentes Internacionales para la biblioteca Unicode) es sólido, IMO.

La encoding UTF-8 tiene muchos méritos, uno de los cuales es que si no te equivocas con los datos (truncándolos, por ejemplo), entonces pueden ser copiados por funciones que no son totalmente conscientes de las complejidades de UTF-8 encoding Esto categóricamente no es el caso con wchar_t .

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

Una de las cosas útiles acerca de los formatos UTF-8, UTF-16 y UTF-32 (donde UTF significa Formato de Transformación Unicode – 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 puedan representar. Tanto UTF-8 como UTF-16 son formatos de múltiples bytes.

UTF-8 es bien conocido por ser un formato de múltiples bytes, con una estructura cuidadosa que hace posible encontrar el inicio de los caracteres en una cadena de manera confiable, comenzando en cualquier punto de la cadena. Los caracteres de un solo byte tienen el bit alto establecido en cero. Los caracteres de varios bytes 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), y los siguientes bytes siempre comienzan en 10. Los caracteres de continuación siempre están en el rango 0x80 .. 0xBF. Hay reglas que los caracteres UTF-8 deben ser representados en el formato mínimo posible. Una consecuencia de estas reglas es que los bytes 0xC0 y 0xC1 (también 0xF5..0xFF) no pueden 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ódigos de 16 bits y todo encajara en un espacio de código de 16 bits. Desafortunadamente, el mundo real es más complejo y tuvo que expandirse a la encoding actual de 21 bits.

UTF-16 es, por lo tanto, un código de unidad única (palabra de 16 bits) para el “Plano multilingüe básico”, es decir, los caracteres con puntos de código Unicode U + 0000 .. U + FFFF, pero utiliza dos unidades (32 bits) para personajes fuera de este rango. Por lo tanto, el código que funciona con la encoding 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 los valores iniciales y finales de las unidades de códigos emparejados en UTF-16. Los sustitutos principales, también llamados altos, son de U + D800 a U + DBFF, y los sustitutos lentos o bajos son de U + DC00 a U + DFFF. Se llaman sustitutos, ya que no representan caracteres directamente, sino solo como un par.

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

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

C11 y

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

  • Caracteres y cadenas de caracteres Unicode ( ) (originalmente especificados en ISO / IEC TR 19769: 2004)

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

6.4.3 Nombres de personajes universales

Sintaxis
nombre universal del personaje:
\u hex-quad
\U hex-quad hex-quad
hex-quad:
dígito hexadecimal hexadecimal-dígito hexadecimal-dígito hexadecimal-dígito

7.28 Utilidades Unicode

El encabezado declara tipos y funciones para manipular caracteres Unicode.

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

 char16_t 

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

 char32_t 

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

(Traducir las referencias cruzadas: define size_t , define mbstate_t , y define uint_least16_t y uint_least32_t .) El también define un conjunto mínimo de (reiniciable) ) funciones de conversión:

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

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

Tenga en cuenta que macOS Sierra (10.12.5), para nombrar solo una plataforma, no admite .

Esta pregunta frecuente 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 tiene 16 bits en Windows, pero no necesariamente 16 bits en otras plataformas. Creo que es un mal necesario en Windows, pero probablemente se pueda evitar en otros lugares. La razón por la que es importante en Windows es que necesita usar archivos que tengan caracteres que no sean ASCII en el nombre (junto con la versión W de las funciones).

  • Tenga en cuenta que las API de Windows que toman cadenas wchar_t esperan la encoding UTF-16. Tenga en cuenta también que esto es diferente de UCS-2. Toma nota de los pares de sustituto. Esta página de prueba tiene pruebas esclarecedoras.

  • Si está progtwigndo en Windows, no puede usar fopen() , fread() , fwrite() , etc. ya que solo toman caracteres char * y no comprenden la encoding UTF-8. Hace que la portabilidad sea dolorosa

Para hacer una progtwigción Unicode estricta:

  • Solo use cadenas API que sean conscientes de Unicode ( NO strlen , strcpy , … pero sus contrapartes de wstrlen wsstrcpy wstrlen , wsstrcpy , …)
  • Al tratar con un bloque de texto, use una encoding que permita almacenar caracteres Unicode (utf-7, utf-8, utf-16, ucs-2, …) sin pérdida.
  • Compruebe que su conjunto de caracteres por defecto del sistema operativo sea compatible con Unicode (por ejemplo, utf-8)
  • Utilice fonts que sean compatibles con Unicode (por ejemplo, arial_unicode)

Las secuencias de caracteres de múltiples bytes son una encoding que es anterior a la encoding UTF-16 (la que se usa normalmente con wchar_t ) y me parece que es más bien solo de Windows.

Nunca he oído hablar de wint_t .

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

Desafortunadamente, C confunde el problema al usar char para “carácter ASCII” e int_least8_t . Querrás hacer algo como:

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

Es posible que también desee typedefs para unidades de código UTF-16 y UTF-32, pero esto es más complicado porque la encoding 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 se define, el tipo _Char16_t existe y es UTF-16.
  • __STDC_UTF_32__ – Si está definido, existe el tipo _Char32_t 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.

¿Esto implica que mi código no debe usar tipos de caracteres en ninguna parte y que se deben usar funciones que puedan manejar wint_t y wchar_t?

Ver también:

  • UTF-8 o UTF-16 o UTF-32 o UCS-2
  • ¿Es necesario wchar_t para la compatibilidad con Unicode?

No. UTF-8 es una encoding Unicode perfectamente válida que usa cadenas de caracteres char* . Tiene la ventaja de que si su progtwig es transparente para bytes que no son ASCII (por ejemplo, un convertidor de final de línea que actúa sobre \r \n pero pasa a través de otros caracteres sin cambios), ¡no tendrá que hacer ningún cambio!

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

Si usa UTF-32, tendrá la simplicidad de caracteres de ancho fijo (pero no grafemas de ancho fijo, pero tendrá que cambiar el tipo de todas sus cadenas).

Si va con UTF-16, tendrá que descartar tanto la suposición de caracteres de ancho fijo como 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 de las codificaciones de un solo byte.

Recomendaría evitar activamente a wchar_t porque no es multiplataforma: a veces es UTF-32, a veces es UTF-16 y algunas veces es una encoding pre-Unicode de Asia oriental. Yo recomendaría usar typedefs

Aún más importante, evite TCHAR .

Básicamente, debes tratar con cadenas en la 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 encoding más común) que es lo suficientemente simple de implementar. Simplemente busca los RFC. Entonces, en la memoria, nada debe ser de múltiples bytes. Un wchar_t representa un personaje. Sin embargo, cuando se trata de serializar, es cuando se necesita codificar algo como UTF-8, donde algunos caracteres están representados por múltiples bytes.

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

Y cuando se trata de sizeof (wchar_t) (necesitarás 4 bytes si quieres hacerlo bien) siempre puedes redefinirlo a un tamaño mayor con typedef / macro hacks si es necesario.

No confiaría en ninguna implementación de biblioteca estándar. Simplemente despliega tus propios tipos Unicode.

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

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