El triste destino de los especificadores de formato de función printf para caracteres Unicode en Visual C ++

El soporte Unicode en Windows apareció antes que en la mayoría de los otros sistemas operativos. Debido a esto, muchos problemas asociados con la representación de caracteres en Windows no se resolvieron de la misma manera que en otros sistemas cuyos desarrolladores pospusieron la implementación del nuevo estándar hasta tiempos mejores [1]. El ejemplo más revelador: en Windows, la codificación UCS-2 se usa para representar caracteres Unicode. El Consorcio Unicode lo recomendó porque la versión 1.0 solo admitía 65.536 caracteres [2]. Cinco años más tarde, el Consorcio cambió de opinión, pero para entonces ya era demasiado tarde para cambiar algo en Windows, ya que Win32s, Windows NT 3.1, Windows NT 3.5, Windows NT 3.51 y Windows 95 ya se habían lanzado al mercado: todos usaban codificación UCS -2 [3].

Pero hoy hablaremos sobre las cadenas de formato de la función printf .

Dado que Unicode se adoptó en Windows antes que en C, esto significaba que los desarrolladores de Microsoft tenían que descubrir cómo implementar el soporte para este estándar en el tiempo de ejecución de C. Como resultado, aparecieron características como wcscmp , wcschr y wprintf . En cuanto a las cadenas de formato en printf , se introdujeron los siguientes calificadores para ellos:

  • % s representa una cadena del mismo ancho que la cadena de formato;
  • % S representa una cadena con el ancho inverso al ancho de la cadena de formato;
  • % hs representa una cadena regular independientemente del ancho de la cadena de formato;
  • % ws y % ls representan una cadena ancha independientemente del ancho de la cadena de formato.

La idea era escribir código como este:

TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %s.\n"), buffer); 

Y al compilar en modo ANSI, obtenga este resultado:

 char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %s.\n", buffer); 

Y al compilar en modo Unicode, esto [4]:

 wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer); 

Como el especificador % s acepta una cadena del mismo ancho que la cadena de formato, este código funcionará correctamente en los formatos ANSI y Unicode. Además, esta solución simplifica en gran medida la conversión del código ya escrito del formato ANSI al formato Unicode, ya que una cadena del ancho requerido se sustituye por el especificador % s .

Cuando el soporte Unicode se agregó oficialmente a C99, el comité de estandarización del lenguaje C adoptó un modelo de cadena de formato diferente para la función printf :

  • % sy % hs representan una cadena regular;
  • % ls representa una cadena ancha.

Ahí es donde comenzaron los problemas. En los últimos seis años, para ese momento, se escribieron una gran cantidad de programas con un volumen de miles de millones de líneas para Windows, y utilizaron el formato anterior. ¿Cómo ser compiladores de Visual C y C ++?

Se decidió permanecer en el viejo modelo no estándar, para no romper todos los programas de Windows existentes en el mundo.

Si desea que su código funcione tanto en entornos de tiempo de ejecución que cumplan con las reglas clásicas para printf como en las que siguen las reglas del estándar C, tendrá que restringirse a los especificadores % hs para cadenas regulares y % ls para cadenas anchas. En este caso, la constancia de los resultados está garantizada, independientemente de si la cadena de formato se pasa a la función sprintf o wsprintf .

 #ifdef UNICODE #define TSTRINGWIDTH TEXT("l") #else #define TSTRINGWIDTH TEXT("h") #endif TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %") TSTRINGWIDTH TEXT("s\n"), buffer); char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %hs\n", buffer); wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf("The string is %ls\n", buffer); 

Una definición TSTRINGWIDTH separada le permite escribir, por ejemplo, este código:

 _tprintf(TEXT("The string is %10") TSTRINGWIDTH TEXT("s\n"), buffer); 

Como a la gente le gusta la presentación tabular de información, aquí hay una tabla para usted.


Destaqué líneas con calificadores, que se definen en C de la misma manera que en el formato clásico adoptado en Windows [5]. Use estos calificadores si desea que su código produzca los mismos resultados en ambos formatos.

Notas

[1] Parece que la introducción de Unicode en Windows antes que otros sistemas debería haberle dado a Microsoft la ventaja del primer movimiento, pero, al menos en el caso de Unicode, se convirtió en una "maldición del pionero" para ellos, porque el resto decidió esperar hasta tiempos mejores, cuando habrá soluciones más prometedoras (como la codificación UTF-8), y solo después de eso, introduzca Unicode en sus sistemas.

[2] Aparentemente, creían que 65,536 personajes deberían haber sido suficientes para todos .

[3] Más tarde fue reemplazado por UTF-16. Afortunadamente, UTF-16 es compatible con versiones anteriores de UCS-2 para aquellos caracteres de código que se pueden representar en ambas codificaciones.

[4] Formalmente, la versión Unicode debería verse así:

 unsigned short buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer); 

El hecho es que wchar_t aún no era un tipo independiente, y hasta que se agregó al estándar, era solo un sinónimo de corto sin signo . Los giros y vueltas del destino wchar_t se pueden encontrar en un artículo separado .

[5] El formato clásico desarrollado por Windows apareció primero, por lo que era más probable que el estándar C tuviera que adaptarse a él, y no al revés.

Nota del traductor

Agradezco al autor por esta publicación. Ahora quedó claro cómo resultó toda esta confusión con "% s". El hecho es que nuestros usuarios preguntan constantemente por qué PVS-Studio reacciona de manera diferente a su código "portátil", según les parece, dependiendo de si recopilan su proyecto en Linux o Windows. Era necesario crear una sección especial separada en la descripción de los diagnósticos V576 dedicados a este tema (ver "Líneas anchas"). Después de este artículo, todo se vuelve aún más claro y obvio. Creo que esta nota debería leerse para todos los que desarrollen aplicaciones multiplataforma. Lee y dile a tus colegas.

Source: https://habr.com/ru/post/466875/


All Articles