Unicode e UTF-8

  ! ? @ + - * / = < >
  0 1 2 3 4 5 6 7 8 9
  A E I O U C …
  Á Â Ã É Í Ó Ô Õ Ú Ç …
  ≡ ≠ ≤ ≥
  Γ Δ Π Σ Ω
  ⋮
  

Esta página faz uma rápida introdução aos conceitos de caractere, Unicode, esquema de codificação, e UTF-8.  Para uma introdução mais completa ao assunto veja os artigos indicados no fim da página.

Caracteres

Um caractere é um símbolo tipográfico usado para escrever texto em alguma língua.  (Embora imperfeita, essa definição é suficiente para nossas necessidades imediatas.)  Eis alguns exemplos de caracteres:

! " - 9 A B a b ~ À ã ç é ÿ Σ α — “

O número de caracteres usados pelas diferentes línguas do mundo é muito grande.  O português usa apenas 127 caracteres e o inglês fica satisfeito com 94 desses. Mas não podemos nos limitar a essas duas línguas porque no mundo globalizado de hoje estamos expostos a muitas outras línguas, às vezes várias numa mesma sentença.

Para começar a organizar essa Babel, é preciso dar nomes a todos os caracteres.  O consórcio Unicode atribuiu nomes numéricos (conhecidos como code points) a mais de 1 milhão de caracteres.  Segue uma minúscula amostra da lista de caracteres e seus números:

número
Unicode
 
caractere
33 !
34 "
45 -
57 9
65 A
66 B
97 a
98 b
126 ~
192 À
227 ã
231 ç
233 é
255 ÿ
931 Σ
945 α
8212
8220

Nessa amostra, os nomes numéricos dos caracteres estão escritos em notação decimal. Em geral, entretanto, esses números são escritos em notação hexadecimal. Além disso, é usual acrescentar o prefixo U+ a cada número:

número
Unicode
 
caractere
U+0021 !
U+0022 "
U+002D -
U+0039 9
U+0041 A
U+0042 B
U+0061 a
U+0062 b
U+007E ~
U+00C0 À
U+00E3 ã
U+00E7 ç
U+00E9 é
U+00FF ÿ
U+03A3 Σ
U+03B1 α
U+2014
U+201C

A lista completa de caracteres e seus números Unicode pode ser vista na página List of Unicode characters da Wikipedia ou na página Unicode / Character reference do Wikibooks.

O conjunto de todos os caracteres da lista Unicode pode ser chamado alfabeto Unicode e cada caractere desse alfabeto pode ser chamado caractere Unicode.  (Se a pretensão do projeto Unicode for justificada, todos os caracteres de todas as línguas do mundo são caracteres Unicode.)

É cômodo usar atalhos verbais óbvios ao falar de caracteres.  Por exemplo, em vez de dizer o caractere A podemos dizer

Caracteres ASCII

Os primeiros 128 caracteres da lista Unicode são os mais usados.  Esse conjunto de caracteres vai de U+0000 a U+007F e é conhecido como alfabeto ASCII.  Os elementos desse alfabeto serão chamados caracteres ASCII.  O alfabeto ASCII contém letras, dígitos decimais, sinais de pontuação, e alguns caracteres especiais. A lista dos 128 caracteres ASCII e seus números Unicode está registrada na Tabela ASCII.

Infelizmente o alfabeto ASCII não é suficiente para escrever texto em português, pois não contém letras com sinais diacríticos.

Esquemas de codificação

Um esquema de codificação (= character encoding) é uma tabela que associa uma sequência de bytes com cada número Unicode, e portanto com cada caractere Unicode.  Em geral, omitimos esquema e dizemos apenas codificação ou código.

A sequência de bytes associada com um caractere é o código do caractere.  Esse código representa o caractere na memória do computador e em arquivos digitais.

Código ASCII

O código ASCII é muito simples: o número Unicode de cada caractere é escrito em notação binária.  Esse código é usado apenas para o alfabeto ASCII.  Como o alfabeto tem apenas 128 caracteres, o código ASCII necessita de apenas 1 byte por caractere e o primeiro bit desse byte é 0.  Segue uma amostra da tabela de códigos:

número
Unicode
 
caractere
 
código ASCII
 
hexadecimal
U+0021 ! 00100001 0x21
U+0022 " 00100010 0x22
U+002D - 00101101 0x2D
U+0039 9 00100111 0x39
U+0041 A 01000001 0x41
U+0042 B 01000010 0x42
U+0061 a 01100001 0x61
U+0062 b 01100010 0x62
U+007E ~ 01111110 0x7E

A última coluna traz o código ASCII escrito em notação hexadecimal.

(Por que não aproveitar todos os 8 bits de um byte?  Com isso, poderíamos codificar 128 caracteres adicionais além dos 128 do alfabeto ASCII.  O código ISO-LATIN-1 faz exatamente isso, mas caiu em desuso.)

Código UTF-8

O alfabeto Unicode tem mais de 1 milhão caracteres. Portanto, o código de cada caractere precisaria de pelo menos 3 bytes se usássemos notação binária. Usar um número fixo de bytes por caractere não seria eficiente, já que 1 byte é suficiente para codificar os caracteres mais comuns.  A solução é recorrer a um código multibyte, que emprega um número variável de bytes por caractere: alguns caracteres usam 1 byte, outros usam 2 bytes, e assim por diante.

O código multibyte mais usado é conhecido como UTF-8.  Ele associa uma sequência de 1 a 4 bytes (8 a 32 bits) com cada caractere Unicode.  Os primeiros 128 caracteres usam o velho e bom código ASCII de 1 byte por caractere.  Os demais caracteres têm um código mais complexo.  Veja uma minúscula amostra:

número
Unicode
 
caractere
 
código UTF-8
 
hexadecimal
U+0021 ! 00100001 0x21
U+0022 " 00100010 0x22
U+002D - 00101101 0x2D
U+0039 9 00100111 0x39
U+0041 A 01000001 0x41
U+0042 B 01000010 0x42
U+0061 a 01100001 0x61
U+0062 b 01100010 0x62
U+007E ~ 01111110 0x7E
U+00C0 À 11000011 01000000 0xC380
U+00E3 ã 11000011 10100011 0xC3A3
U+00E7 ç 11000011 10100111 0xC3A7
U+00E9 é 11000011 10101001 0xC3A9
U+00FF ÿ 11000011 10111111 0xC3BF
U+03A3 Σ 11001110 10100011 0xCEA3
U+03B1 α 11001110 10110001 0xCEB1
U+2014 11100010 10000000 10010100 0xE28094
U+201C 11100010 10000000 10011100 0xE2809C

(A última coluna traz o código UTF-8 escrito em notação hexadecimal.)  A lista dos códigos UTF-8 de todos os caracteres Unicode pode ser vista na página UTF-8 encoding table and Unicode characters ou na página Unicode / Character reference do Wikibooks.   Por exemplo, a cadeia de caracteres  ação  é representada em UTF-8 pela seguinte sequência de bytes:

0x61 0xC3 0xA7 0xC3 0xA3 0x6F
a ç ã o

Observe que todas as letras com sinais diacríticos usadas em português são representados em UTF-8 por apenas 2 bytes, o primeiro dos quais é 0xC3 (195 em notação decimal).

Decodificação.  Como o número de bytes por caractere não é fixo, a decodificação de uma sequência de bytes que representa um texto não é trivial.  Como saber onde termina o código de um caractere e começa o código do caractere seguinte?   O esquema de codificação UTF-8 foi construído de modo que os primeiros bits do código de um caractere dizem quantos bytes o código ocupa.  Assim, se o primeiro bit é 0, e portanto o valor do primeiro byte é menor que 128, então esse é o único byte do caractere.  Se o valor do primeiro byte pertence ao intervalo 192 .. 223 então o código do caractere tem dois bytes.  E assim por diante.

A linguagem C não prescreve nenhum esquema de codificação específico. Mas o código mais usado é UTF-8, tanto para entrada e saída quanto para a representação interna de caracteres.  O presente sítio supõe que todos os arquivos (programas e dados) do leitor usam código UTF-8 (ou o subconjunto ASCII de UTF-8).

Exercícios 1

  1. A seguinte sequência de bytes está em notação decimal. Que cadeia de caracteres esses bytes representam em código UTF-8?
    67 195 179 100 105 103 111
    
  2. A seguinte sequência de bytes está em notação hexadecimal. Que cadeia de caracteres esses bytes representam em código UTF-8?
    0x41 0x74 0x65 0x6E 0xC3 0xA7 0xC3 0xA3 0x6F 0x21
    
  3. Consulte a tabela UTF-8 encoding table and Unicode characters (ou outra semelhante) para verificar que todas as letras com sinais diacríticos usadas em português são representados em UTF-8 por apenas 2 bytes, o primeiro dos quais é 0xC3 (195 em notação decimal).
  4. Escreva uma função que receba um arquivo que contém texto em código UTF-8 e decida se cada byte do arquivo representa um único caractere (ou seja, se o alfabeto do arquivo é ASCII).

Como o meu arquivo está codificado?

Todo arquivo de texto é uma sequência de bytes.  Não há como saber, com certeza, qual o esquema de codificação usado por um dado arquivo.  Portanto, não há como saber, com segurança, que cadeia de caracteres o arquivo representa.  O autor do arquivo precisa informar, fora do arquivo, qual código usou.

Há utilitários (como o file, por exemplo) que examinam um arquivo e tentam adivinhar, com algum grau de confiança, o seu esquema de codificação.

Se souber qual o esquema de codificação usado por seu arquivo, você pode usar o aplicativo iconv para mudar a codificação (convertendo, por exemplo, um arquivo codificado em ISO-LATIN-1 em um arquivo equivalente codificado em UTF-8).

Exercícios 2

  1. Familiarize-se com os programas od e hexdump.  Esses utilitários imprimem a sequência de bytes de um arquivo sem convertê-los em caracteres.
  2. A função isalpha da biblioteca ctype só reconhece letras do alfabeto ASCII (sem sinais diacríticos, portanto).  Escreva uma extensão de isalpha que reconheça letras com diacríticos.  Sua função deve receber uma string que seja a codificação UTF-8 de um caractere e decidir se o código representa uma letra (com sinal diacrítico ou não).

Locales

O comportamento de qualquer programa depende do ambiente criado pelo sistema operacional.  Esse ambiente é definido pelas variáveis de ambiente (= environment variables) do sistema.  As variáveis especificam a língua e o esquema de codificação padrão.  Digite locale no terminal para obter o valor de todas as variáveis de ambiente:

~$ locale
LANG=pt_US.UTF-8
LANGUAGE=en_US:en_GB:en
LC_CTYPE=pt_US.UTF-8
LC_NUMERIC=en_US.UTF-8
LC_TIME=pt_US.UTF-8
LC_COLLATE=pt_US.UTF-8
LC_MONETARY=pt_BR.UTF-8
LC_MESSAGES=pt_BR.UTF-8
LC_MEASUREMENT=pt_BR.UTF-8
LC_ALL=

A variável de ambiente mais relevante para o comportamento de printf, scanf, etc. é LC_CTYPE.  Suporemos que LC_CTYPE vale pt_BR.UTF-8 ou en_US.UTF-8 no seu sistema.

O presente sítio supõe que o leitor usa o sistema operacional Linux com locale UTF-8

Suporemos também que todos os programas do leitor que manipulam texto invocam a função setlocale para importar a variável de ambiente LC_CTYPE e outras variáveis relevantes.

Você também pode mudar o valor de variáveis de ambiente temporariamente antes de invocar o seu programa. Por exemplo,

~$ LC_COLLATE=pt_BR.UTF-8 ./meuprograma

ou LC_COLLATE=C, ou LC_ALL=C, ou…

Exemplo.  Considere o seguinte programa:

#include <locale.h>

int main (void) {
   setlocale (LC_ALL, ""); // importa as variáveis de ambiente
   setlocale (LC_CTYPE, "pt_BR.UTF-8"); // por via das dúvidas
   char buffer[100];
   scanf ("%s", buffer); 
   printf ("%s\n", buffer);
   return EXIT_SUCCESS;
}

Suponha que o usuário invoca o programa de modo que um arquivo entra.txt seja usado no papel de stdin.  Se o arquivo entra.txt contiver

áéíóú

e estiver codificado em UTF-8, o programa imprimirá

áéíóú

Note que a primeira linha do arquivo entra.txt tem 5 letras acentuadas (seguidas de um \n) e portanto tem 11 bytes:  2 bytes para cada letra e mais 1 byte para \n.  A string armazenada no vetor buffer tem 11 bytes:  os 10 bytes das letras acentuadas mais o byte nulo \0 que marca o fim da string.