Codificação mais comum para strings em C ++ no Linux (e Unix?)

6

Para criar um programa C ++ que seja portátil em nível de código-fonte entre o Windows e o Linux e lide bem com a internacionalização, há três codificações principais do IMHO a serem consideradas:

  • A codificação do código-fonte do C ++.
  • A codificação de dados externos.
  • A (s) codificação (ões) de strings e literais.

Para o código-fonte C ++, não há realmente nenhuma alternativa ao UTF-8 com BOM, pelo menos se os literais padrão de entrada e cadeia larga funcionarem na plataforma Windows. UTF-8 sem BOM faz com que o compilador Visual C ++ da Microsoft assuma a codificação ANSI do Windows para o código fonte, o que é bom para a saída UTF-8 via std::cout , no grau limitado que funciona (janelas do console do Windows tem muitos bugs aqui) . No entanto, a entrada via std::cin não funciona.

E para os dados externos, o UTF-8 parece ser o padrão de fato.

No entanto, e os literais e strings internos? Aqui eu tive a impressão de que strings estreitas codificadas como UTF-8 eram a convenção comum no Linux. Mas recentemente duas pessoas diferentes afirmaram o contrário, uma afirmando que a convenção comum para cadeias internas em aplicativos internacionais no Linux é UTF-32, e a outra apenas alegando que há alguma diferença não especificada entre Unix e Linux nesta área.

Como alguém que mexer um pouco, numa base de hobby, com uma micro-biblioteca destinada a abstrair as diferenças do Windows / Linux nesta área, eu tenho que perguntar concretamente em

  • qual é a convenção comum do Linux para representar strings em um programa?

Tenho certeza de que existe uma convenção comum que é tão comum que essa pergunta tenha uma Resposta Real ™.

Um exemplo mostrando, por exemplo como inverter convencionalmente uma cadeia de caracteres (que é complexo para fazer diretamente com UTF-8, mas que presumivelmente é feito por funções que são de fato padrão no Linux?), também seria bom, ou seja, como uma pergunta, o que é uma versão convencional do Linux deste programa C ++ (o código como dado funciona para o Latin-1 como o conjunto de caracteres de execução de texto restrito em C ++):

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

#define STATIC_ASSERT( cond )   static_assert( cond, #cond )

int main()
{
    string line;
    if( getline( cin, line ) )
    {
        static char const aSingleChar[] = "æ";
        STATIC_ASSERT( sizeof( aSingleChar ) - 1 == 1 );
        reverse( line.begin(), line.end() );

        cout << line << endl;
    }
}
    
por Alf P. Steinbach 13.11.2011 / 14:24

3 respostas

6

Para representações externas, o UTF-8 é definitivamente o padrão. Algumas codificações de 8 bits ainda são strongs (principalmente na Europa) e algumas codificações de 16 bits ainda são strongs (principalmente no leste da Ásia), mas são codificações claramente legadas, em sua saída lenta. O UTF-8 é padrão não apenas no unix, mas também na web.

Para representações internas, não existe um padrão tão grande. Você encontrará alguns UTF-8, alguns UCS-2, alguns UTF-16 e alguns UCS-4, se você olhar em volta.

  • O UTF-8 tem a vantagem de corresponder à representação comum e é um superconjunto do ASCII. Em particular, é a única codificação aqui onde um caractere nulo corresponde a um byte nulo, o que é importante se você tiver APIs C ao redor (incluindo chamadas de sistema unix e funções de biblioteca padrão).
  • UCS-2 é uma sobrevivência histórica. Foi atraente porque foi pensado como uma codificação de largura fixa, mas não pode representar todo o Unicode, que é um limitador.
  • As principais reivindicações da UTF-16 para a fama são APIs Java e Windows. As APIs do Unix (que gostam de UTF-8) são mais relevantes que as APIs do Windows se você estiver programando para o unix. Somente programas voltados para interação com APIs que gostam de UTF-16 tendem a usar UTF-16.
  • O UCS-4 é atraente porque parece uma codificação de largura fixa. A coisa é, não é, na verdade. Por causa da combinação de caracteres, não existe uma codificação Unicode de largura fixa.
  • Há também wchar_t . O problema é que são 2 bytes em algumas plataformas e 4 bytes em outros, e o conjunto de caracteres que ele representa não é específico. Com o Unicode sendo o conjunto de caracteres padrão de fato, os aplicativos mais novos tendem a evitar wchar_t .

No mundo unix, o argumento que prevalece sobre todos eles é geralmente a compatibilidade com APIs unix, apontando para UTF-8. Não é universal, no entanto, não há resposta do tipo "sim ou não" para saber se a sua biblioteca precisa suportar outras codificações.

Não há diferença entre variantes unix nesse aspecto. O Mac OS X prefere caracteres decompostos para ter uma representação normalizada, então você pode querer fazer isso também: Isso economizará algum trabalho no OSX e não importará em outros unices.

Observe que não existe uma lista técnica em UTF-8. Uma marca de ordem de byte só faz sentido para codificações de tamanho super-byte. O requisito de que os arquivos codificados em UTF-8 comecem com o caractere U + FEFF é específico de alguns aplicativos da Microsoft.

    
por 13.11.2011 / 15:15
5

Esta é apenas uma resposta parcial, já que sua pergunta é bastante ampla.

C ++ define um "conjunto de caracteres de execução" (na verdade, dois deles, um estreito e um largo).

Quando seu arquivo de origem contém algo como:

char s[] = "Hello";

Em seguida, o valor do byte numérico das letras na string literal é simplesmente consultado de acordo com a codificação de execução. (A codificação de execução separada ampla aplica-se ao valor numérico atribuído às constantes de caracteres largos L'a' .)

Tudo isso acontece como parte da leitura inicial do arquivo de código-fonte no processo de compilação. Uma vez dentro, os caracteres C ++ são nada mais que bytes, sem semântica conectada. (O nome do tipo char deve ser um dos erros mais graves em linguagens derivadas de C!)

Existe uma exceção parcial em C ++ 11, onde os literais u8"" , u"" e U"" determinam o valor resultante dos elementos string (ou seja, os valores resultantes são globalmente não ambíguo e independente de plataforma), mas isso não afeta como o código-fonte de entrada é interpretado.

Um bom compilador deve permitir que você especifique a codificação do código-fonte, assim, mesmo que seu amigo em uma máquina EBCDIC lhe envie o texto do programa, isso não deve ser um problema. O GCC oferece as seguintes opções:

  • -finput-charset : conjunto de caracteres de entrada, ou seja, como o arquivo de código-fonte é codificado
  • -fexec-charset : conjunto de caracteres de execução, ou seja, como codificar literais de string
  • -fwide-exec-charset : conjunto de caracteres de execução ampla, por exemplo, como codificar literais de cadeia larga

O GCC usa iconv() para as conversões, portanto, qualquer codificação aceita por iconv() pode ser usada para essas opções.

Eu escrevi anteriormente sobre alguns recursos opacos fornecidos pelo padrão C ++ para lidar com texto codificações.

Exemplo: pegue o código acima, char s[] = "Hello"; . Suponha que o arquivo de origem seja ASCII (ou seja, a codificação de entrada é ASCII). Em seguida, o compilador lê 99 e interpreta como c e assim por diante. Quando se trata do literal, ele lê 72 , interpreta como H . Agora ele armazena o valor de byte de H na matriz que é determinada pela codificação de execução (novamente 72 se for ASCII ou UTF-8). Quando você escreve \xFF , o compilador lê 99 120 70 70 , decodifica como \xFF e escreve 255 na matriz.

    
por 13.11.2011 / 15:00
1

one claiming that the common convention for internal strings in international applications in Linux is UTF-32

Esta é provavelmente uma referência ao fato de o GCC definir wchar_t como um caractere UTF-32, diferentemente dos compiladores do Windows C (++) que definem wchar_t = UTF-16 (para compatibilidade com o Windows WCHAR ) .

Você poderia usar wchar_t internamente, se isso for conveniente para você. No entanto, não é tão comum no mundo * nix quanto no mundo Windows, porque a API POSIX nunca foi reescrita para usar caracteres largos como o Windows.

Usar o UTF-8 internamente funciona bem para rotinas que são "neutras em codificação". Por exemplo, considere um programa para converter planilhas separadas por tabulações em CSV. Você precisaria tratar os caracteres ASCII \t , , e " especialmente, mas todos os bytes no intervalo não-ASCII (se eles representam caracteres ISO-8859-1 ou unidades de código UTF-8) podem simplesmente ser copiado como está.

As one who fiddles a little, on a hobby basis, with a micro-library intended to abstract away the Windows/Linux differences in this area,

Um dos muitos aborrecimentos de escrever códigos de plataforma cruzada é que no Windows é fácil usar o UTF-16 e usar o UTF-8, mas vice-versa no Linux. Eu lidei com isso escrevendo funções como esta:

FILE* fopen_utf8(const char* filename, const char* mode)
{
#ifdef _WIN32
    std::wstring wfilename = ConvertUtf8ToUtf16(filename);
    std::wstring wmode = ConvertUtf8ToUtf16(mode);
    return _wfopen(wfilename.c_str(), wmode.c_str());
#else
    return fopen(filename, mode);
#endif
}
    
por 14.11.2011 / 08:59