O triste destino dos especificadores de formato de função printf para caracteres Unicode no Visual C ++

O suporte Unicode no Windows apareceu mais cedo do que na maioria dos outros sistemas operacionais. Por esse motivo, muitos problemas associados à representação de caracteres no Windows não foram resolvidos da mesma maneira que em outros sistemas cujos desenvolvedores adiaram a implementação do novo padrão para tempos melhores [1]. O exemplo mais revelador: no Windows, a codificação UCS-2 é usada para representar caracteres Unicode. Foi recomendado pelo Unicode Consortium porque a versão 1.0 suportava apenas 65.536 caracteres [2]. Cinco anos depois, o Consortium mudou de idéia, mas já era tarde para mudar alguma coisa no Windows, já que o Win32s, o Windows NT 3.1, o Windows NT 3.5, o Windows NT 3.51 e o Windows 95 já haviam sido lançados no mercado - todos eles usavam codificação UCS -2 [3].

Hoje, porém, falaremos sobre as cadeias de formato da função printf .

Como o Unicode foi adotado no Windows mais cedo que no C, isso significava que os desenvolvedores da Microsoft tinham que descobrir como implementar o suporte a esse padrão no tempo de execução C. Como resultado, recursos como wcscmp , wcschr e wprintf apareceram . Quanto à formatação de strings no printf , os seguintes qualificadores foram introduzidos para eles:

  • % s representa uma sequência da mesma largura que a sequência de formatação;
  • % S representa uma string com a largura inversa à largura da string de formato;
  • % hs representa uma sequência regular, independentemente da largura da sequência de formatação;
  • % ws e % ls representam uma cadeia larga, independentemente da largura da cadeia de formato.

A ideia era escrever código assim:

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

E ao compilar no modo ANSI, obtenha este resultado:

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

E ao compilar no modo Unicode - este [4]:

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

Como o especificador % s aceita uma sequência da mesma largura que a sequência de formato, esse código funcionará corretamente nos formatos ANSI e Unicode. Além disso, esta solução simplifica bastante a conversão do código já gravado do formato ANSI para o formato Unicode, pois a cadeia de caracteres da largura necessária é substituída pelo especificador % s .

Quando o suporte a Unicode foi oficialmente adicionado ao C99, o comitê de padronização da linguagem C adotou um modelo de string de formato diferente para a função printf :

  • % se % hs representam uma sequência regular;
  • % ls representa uma cadeia larga.

É aí que os problemas começaram. Nos últimos seis anos, um grande número de programas com um volume de bilhões de linhas foi gravado no Windows e eles usaram o formato antigo. Como ser compiladores Visual C e C ++?

Foi decidido permanecer no modelo antigo e não padrão, para não interromper todos os programas Windows existentes no mundo.

Se você deseja que seu código funcione nos ambientes de tempo de execução que seguem as regras clássicas para printf e nos que seguem as regras do padrão C, você deverá se restringir aos especificadores % hs para cadeias regulares e % ls para cadeias amplas. Nesse caso, a constância dos resultados é garantida, independentemente de a cadeia de formato ser passada para a função sprintf ou 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); 

Uma definição TSTRINGWIDTH separada permite que você escreva, por exemplo, este código:

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

Como as pessoas gostam da apresentação tabular de informações, aqui está uma tabela para você.


Eu destaquei linhas com qualificadores, que são definidos em C da mesma maneira que no formato clássico adotado no Windows [5]. Use esses qualificadores se desejar que seu código produza os mesmos resultados nos dois formatos.

Anotações

[1] Parece que a introdução do Unicode no Windows antes de outros sistemas deveria ter dado à Microsoft a vantagem da primeira jogada, mas - pelo menos no caso do Unicode - se transformou em uma "maldição do pioneiro" para eles, porque o resto decidiu esperar apenas por melhores tempos, quando haverá soluções mais promissoras (como a codificação UTF-8) e somente depois disso introduziremos o Unicode em seus sistemas.

Aparentemente, eles acreditavam que 65.536 caracteres deveriam ter sido suficientes para todos .

[3] Mais tarde foi substituído por UTF-16. Felizmente, o UTF-16 é compatível com o UCS-2 para os caracteres de código que podem ser representados nas duas codificações.

[4] Formalmente, a versão Unicode deve ficar assim:

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

O fato é que wchar_t ainda não era um tipo independente e, até ser adicionado ao padrão, era apenas um sinônimo de atalho não assinado . As reviravoltas do destino wchar_t podem ser encontradas em um artigo separado .

[5] O formato clássico desenvolvido pelo Windows apareceu primeiro, então era mais provável que o padrão C tivesse que se adaptar a ele, e não vice-versa.

Nota do tradutor

Sou grato ao autor por esta publicação. Agora ficou claro como toda essa confusão com "% s" acabou. O fato é que nossos usuários constantemente perguntavam por que o PVS-Studio reage de maneira diferente ao seu código “portátil”, como parece a eles, dependendo se eles coletam seu projeto no Linux ou Windows. Foi necessário criar uma seção separada especial na descrição dos diagnósticos do V576 dedicados a este tópico (consulte "Linhas largas"). Após este artigo, tudo se torna ainda mais claro e óbvio. Acho que esta nota deve ser lida para todos que desenvolvem aplicativos de plataforma cruzada. Leia e conte aos colegas.

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


All Articles