−2147483648 . . 2147483647
−128 . . 127
Na linguagem C, os números naturais são conhecidos como inteiros-sem-sinal, enquanto os números inteiros são conhecidos como inteiros-com-sinal. Inteiros-sem-sinal são implementados pelos tipos-de-dados unsigned char e unsigned int. Inteiros-com-sinal são implementados pelos tipos-de-dados char e int. Para criar variáveis u, n, c e i dos quatro tipos, basta escrever
unsigned char u; unsigned int n; char c; int i;
(No lugar de unsigned int
podemos escrever, simplesmente, unsigned
.)
Existem ainda os tipos
short int
e long int
e as correspondentes versões com prefixo unsigned
,
mas esses tipos serão usados apenas marginalmente neste sítio.
Sumário:
Um unsigned char é um inteiro-sem-sinal no intervalo 0 . . 28−1, ou seja, no intervalo 0 . . 255 Cada unsigned char é implementado em 1 byte, usando notação binária.
Os inteiros fora do intervalo 0 . . 255 são reduzidos módulo 28, ou seja, representados pelo resto da divisão por 256. Em outras palavras, todo inteiro-sem-sinal N é representado pelo unsigned char u tal que a diferença N − u é um múltiplo de 256.
Exemplo. Para mostrar um exemplo que caiba na página, fingiremos que cada byte tem apenas 4 bits. Nessas condições, o valor de qualquer unsigned char pertence ao intervalo 0 . . 24−1. Esse intervalo pode ser representado por um círculo de modo a sugerir a redução módulo 24:
0 15 1 14 2 13 3 12 4 11 5 10 6 9 7 8 |
Por exemplo, 16 é representado por 0 pois 16 = 1×24 + 0. Da mesma forma, 17 é representado por 1 pois 17 = 1×24 + 1. Analogamente, 36 é representado por 4 pois 36 = 2×24 + 4.
Caracteres. O tipo unsigned char não representa caracteres. Mas os números do tipo unsigned char menores que 128 são usados para representar os caracteres do alfabeto ASCII.
Um char é um inteiro-com-sinal no intervalo −27 . . 27−1, ou seja, em −128 . . 127. Cada char é implementado em 1 byte usando notação complemento-de-dois. (Na verdade, o padrão da linguagem C não exige que os valores de char estejam no intervalo −27 . . 27−1. Algumas implementações podem usar o intervalo 0 . . 28−1. Mas vamos ignorar essa possibilidade.)
Inteiros fora do intervalo −128 . . 127 são reduzidos módulo 28, ou seja, todo inteiro-com-sinal N é representado pelo char c tal que a diferença N − c é um múltiplo inteiro (positivo ou negativo) de 256.
O tipo char
não representa caracteres, pelo menos não necessariamente.
Para enfatizar isso, usaremos
byte
como apelido de
char
:
typedef char byte;
Exemplo. Para efeito deste exemplo, vamos fingir que todo byte tem apenas 4 bits. Nessas condições, o valor de qualquer char pertence ao intervalo −8 . . 7. Esse intervalo pode ser representado por um círculo de modo a sugerir a redução módulo 24:
+0 −1 +1 −2 +2 −3 +3 −4 +4 −5 +5 −6 +6 −7 +7 −8 |
Por exemplo, 8 módulo 24 é −8 (pois 8 = 1×24 − 8), 9 módulo 24 é −7 (pois 9 = 1×24 − 7) e −30 módulo 24 é 2 (pois −30 = −2×24 + 2).
Caracteres. Números do tipo char entre 0 e 127 podem ser usados para representar caracteres do alfabeto ASCII. Nesse intervalo, um char e um unsigned char têm o mesmo padrão de 8 bits e portanto representam o mesmo caractere.
O hardware de todo computador trabalha com blocos de s bytes consecutivos, podendo s valer 1, 2, 4 ou até 8 conforme o computador. Cada bloco de s bytes consecutivos é uma palavra (= word). Cada palavra pode assumir 28s diferentes valores.
Um unsigned int é um inteiro-sem-sinal no intervalo 0 . . 28s−1. Cada unsigned int é implementado em uma palavra usando notação binária. O valor de s é dado pela expressão sizeof (unsigned int) e o número 28s−1 está registrado na constante UINT_MAX, definida na interface limits.h.
Inteiros maiores que UINT_MAX são reduzidos módulo UINT_MAX + 1. Portanto, todo inteiro positivo N é representado pelo unsigned int n para o qual a diferença N − n é um múltiplo inteiro de UINT_MAX + 1.
Desse ponto em diante, os exemplos supõem que s = 4. Assim, UINT_MAX vale 232−1, ou seja, 4294967295.
Aritmética unsigned int. As operações de adição, subtração e multiplicação entre números do tipo unsigned int estão sujeitas a transbordamento (= overflow), pois o resultado exato de uma operação pode fugir do intervalo 0..UINT_MAX. Em geral, evitamos transbordamentos pois trabalhamos com números pequenos. Transbordamentos não são tratados como erros e o resultado exato de cada operação é tacitamente reduzido módulo UINT_MAX + 1. Por exemplo:
unsigned int n, m, x; n = 4000000000; m = 300000000; x = n + m; // overflow // x vale 5032704
A operação de divisão entre unsigned ints não está sujeita a overflow, mas o quociente é truncado: a expressão 9/2, por exemplo, tem valor ⌊9/2⌋, ou seja, o piso de 9/2.
Um int é um inteiro-com-sinal no intervalo −28s−1 . . 28s−1−1. Cada int é implementado em s bytes consecutivos usando notação complemento-de-dois. O valor de s é dado pela expressão sizeof (int), que é igual a sizeof (unsigned int). Os números −28s−1 e 28s−1−1 estão registrados nas constantes INT_MIN e INT_MAX respectivamente, ambas definidas na interface limits.h. Como seria de se esperar, INT_MAX − INT_MIN é igual a UINT_MAX.
Inteiros fora do intervalo INT_MIN..INT_MAX são reduzidos módulo UINT_MAX + 1. Assim, todo inteiro N é representado pelo int i para o qual a diferença N − i é um múltiplo (positivo ou negativo) de UINT_MAX + 1.
Desse ponto em diante, suporemos que s = 4. Assim, INT_MIN vale −231, ou seja, −2147483648, e INT_MAX vale 231−1, ou seja, 2147483647.
Aritmética int. As operações de adição, subtração e multiplicação entre números do tipo int estão sujeitas a transbordamento (= overflow), pois o resultado exato de uma operação pode fugir do intervalo INT_MIN..INT_MAX. Em geral, evitamos transbordamentos pois trabalhamos com números pequenos. Transbordamentos podem passar despercebidos pois não são tratados como erros e o resultado exato de cada operação é automaticamente reduzido módulo UINT_MAX + 1. Por exemplo:
int i, j, x; i = 2147483000; j = 2147483000; x = i + j; // overflow // x vale -1296
A atribuição de um unsigned int a um int também pode resultar em overflow e portanto é feita módulo UINT_MAX + 1. Por exemplo:
int i; unsigned int n; n = 2147483700; i = n; // overflow // i vale -2147483596
Nas operações de divisão entre ints,
o quociente é truncado:
a expressão 9/2, por exemplo, tem valor
⌊9/2⌋,
ou seja,
o piso
de 9/2.
No caso de números estritamente negativos,
o resultado da divisão é truncado
em direção ao zero:
a expressão -9/2 tem valor −⌊9/2⌋,
não ⌊−9/2⌋.
int main (void) { printf ("sizeof (unsigned): %lu\n", sizeof (unsigned)); printf ("UINT_MAX: %u\n", UINT_MAX); printf ("sizeof (int) = %lu\n", sizeof (int)); printf ("INT_MIN: %d\nINT_MAX: %d\n", INT_MIN, INT_MAX); return EXIT_SUCCESS; }
O código de quase todo programa em C contém constantes inteiras. Muitos programas também têm constantes-caractere. Por exemplo:
a = 999; c = 'a';
As constantes inteiras como 999 no exemplo são tratadas como se fossem do tipo int e devem ter valor entre INT_MIN e INT_MAX.
As constantes-caractere, como 'a' no exemplo acima, são embrulhadas em aspas para que não sejam confundidas com nomes de variáveis. Elas estão restritas ao alfabeto ASCII e portanto expressões como 'Ã' e 'ç' não são válidas. O valor de uma constante-caractere é dado pela tabela ASCII (assim, 'a' é o mesmo que 97).
Constantes-caractere são do tipo int e não do tipo char, como seria de se esperar. Mas quando a constante é atribuída a uma variável do tipo char, ela é convertida em um byte (por exemplo, 'a' é convertida em 01100001).
A título de ilustração, o seguinte fragmento de programa exibe as vinte e seis letras maiúsculas do alfabeto ASCII. Note que essas letras ocupam posições consecutivas na tabela ASCII e estão em ordem alfabética. Algo análogo acontece com as letras minúsculas.
char c; for (c = 'A'; c <= 'Z'; ++c) printf ("%c ", c);
for (int i = 1; i <= 26; ++i) printf ("%c\n", 'a' + i - 1);
Operações aritméticas entre operandos
do tipo char e/ou unsigned char
não são executadas módulo 28,
como poderíamos imaginar.
Nessas operações,
todos os operandos são previamente convertidos
(promovidos
)
ao tipo int
e a operação é executada em aritmética int.
Por exemplo, se as variáveis u e v são do tipo unsigned char e têm valor 255 e 2 respectivamente, a expressão u + v é do tipo int e tem valor 257. (É claro, entretanto, que a atribuição de u + v a uma variável do tipo unsigned char é feita módulo 28.)
Considerações análogas valem para expressões aritméticas sobre operandos de tipos mistos, como int, char e unsigned char. Por exemplo, se a variável c é do tipo char e tem valor 127, a expressão c + 2 é do tipo int e tem valor 129 (a constante 2 é do tipo int por definição).
unsigned char u, v, w; u = 255; v = 2; printf ("%d", u + v); w = u + v; printf ("%d", w);
unsigned char u; for (u = 0; u < 256; ++u) printf (".");
char c; for (c = 0; c < 128; ++c) printf (".");
Algumas aplicações envolvem números inteiros que não cabem em um int. Para atender essas aplicações, a linguagem C tem o tipo-de-dados long int, que ocupa mais bytes que o tipo int. No meu computador, um long int ocupa 8 bytes, ou seja, 64 bits. (Mas veja a página Is there any need of “long” data type in C and C++? em GeeksforGeeks.)
Resposta:
O tipo-de-dados int32_t, por exemplo,
ocupa exatamente 4 bytes
qualquer que seja o valor de sizeof (int).
Os demais tipos são definidos de maneira análoga.
Para algumas aplicações,
o uso desses tipos melhora a portabilidade dos programas.
Entretanto,
estas notas estão mais preocupadas com os algoritmos
que com os detalhes de suas implementações.
Por isso, continuo usado int e unsigned
neste sítio.
(Veja
resposta de Matt Whiting
à pergunta
Why does the C library have their own Int and other datatypes?
no Quora.)