paulo1205
(usa Ubuntu)
Enviado em 23/01/2019 - 23:14h
Você tem, antes de tudo, de lembrar que, embora o nome básico do tipo seja
char (abreviação de
character, que significa caráter, ou um símbolo que pode ser impresso), em C os tipos
char,
signed char e
unsigned char são tipos numéricos, que aceitam operações aritméticas. O nome do tipo faz referência a caracteres porque, no passado (antes de Unicode e antes de haver preocupações com internacionalização), oito bits eram mais do que suficientes para acomodar os caracteres e sinais de controle dos equipamentos de comunicações então existentes.
Quando o C surgiu, a máquina onde foi primeiramente implementado tinhas duas possíveis representação de inteiros: com oito bits ou com dezesseis. Como um dos usos mais comuns de inteiros de menor capacidade era para representar caracteres, decidiu-se chamar o tipo menor de
char e o maior de
int, embora ambos possuam todas as propriedades numéricas e aritméticas.
Poder representar números negativos ou não foi uma coisa que surgiu algum tempo depois, e foi aplicada a todos os tipos numéricos integrais, porque muitas aplicações prescindem de números negativos, e podem se beneficiar de usar um bit a mais para contar o dobro de números não-negativos.
Mas, além do tamanho, uma diferença entre
char e os outros tipos integrais está justamente na representação do tipo
char quando não-explicitamente qualificado com
signed ou
unsigned. O tipo
int, por exemplo, é sempre com sinal, e é um sinônimo exato de “
signed int” e apenas “
signed”, e o mesmo vale para
short,
long e
long long. Com
char, a lógica é outra: cada implementação é livre para decidir se
char se comporta de modo semelhante a
signed char ou como
unsigned char. Nos nossos PCs com Intel, o padrão é ter comportamento de
signed char; em arquiteturas ARM ou Sparc, o padrão é se comportar como
unsigned char.
Porém, mesmo que uma implementação tenha bem claro qual comportamento usar, o tipo
char permanece distinto tanto de
unsigned char quanto de
unsigned char. Isso não-raramente provoca alguma confusão, sobretudo em duas situações: quando existe promoção de
char não-qualificado para
int (ou outro tipo numérico de tamanho maior que o de um único
char) ou quando se tenta converter implicitamente um ponteiro para uma das três variações em uma das outras duas.
Uma fonte muito comum de erro é quando se usam as funções de <ctype.h>. Essas funções esperam receber um valor inteiro que seja exatamente igual a
-1 ou que possa ser ser representado como
unsigned char. Então, se
c é um valor do tipo
char, é ERRADO, produzindo comportamento indefinido (pode funcionar ou não), chamar, por exemplo, “
isalpha(c)” ou “
toupper(c)”. O certo seria transformar explicitamente o valor proveniente de um
char não-qualificado (ou qualificado como
signed) em um valor do tipo
unsigned char (por exemplo, fazendo uma conversão explícita: “
isalpha((unsigned char)c)”) ou trabalhar com
unsigned char o tempo todo. Contudo, trabalhar com
unsigned char o tempo todo é inconveniente porque outras funções da biblioteca padrão, tais como as de <string.h> e <stdlib.h> usam ponteiros para
char não-qualificado, e ponteiros para
unsigned char passados como argumentos para essas funções dariam erro de tipo, a não ser que se fizessem também conversões de tipo explícitas.
No dia-a-dia, o mais comum quando se trabalha com texto é usar
char não-qualificado para caracteres e
arrays de
char não-qualificado para
strings, a fim de manter compatibilidade com as funções de <string.h>, <stdlib.h> e <stdio.h>, entre outras, e usar as variações qualificadas, com ou sem sinal, para representar números que, respectivamente, possam ou não ser negativos. Mas há exceções: uma delas, já mencionada, é quando se precisa das funções de <ctype.h>, que exigem conversão de valor.
... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)