Os tipos int e char

−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 charint.  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:

O tipo unsigned char

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  Nu  é 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.

O tipo char

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  Nc  é 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.

Exercícios 1

  1. Escreva, em notação binária, o menor e o maior valor que um unsigned char pode ter. Repita o exercício com char.
  2. Escreva, em notação hexadecimal, o menor e o maior valor que um unsigned char pode ter. Repita o exercício com char.
  3. Qual o unsigned char que representa o número 1000 (um mil)?  Qual o char que representa 1000?  Qual o char que representa −1000?
  4. Escreva e teste um programa que exiba na tela os caracteres representados pelos chars 32 a 127.  (Veja impressão em formato %c no capítulo Entrada e saída.)  Exiba dez caracteres por linha.
  5. Escreva e teste um programa que tente exibir na tela os caracteres representados pelos bytes cujo primeiro bit é 1.  (Veja impressão em formato %c no capítulo Entrada e saída.)
  6. Escreva um programa que receba dois caracteres do alfabeto ASCII digitados pelo usuário no teclado e diga se o primeiro vem antes ou depois do segundo na tabela ASCII. (Veja leitura em formato %c no capítulo Entrada e saída.)
  7. Biblioteca ctype.  Familiarize-se com as funções da biblioteca ctype.  A função isspace, por exemplo, decide se um dado caractere ASCII é um branco. A função isalpha decide se um dado caractere ASCII é uma letra.

O tipo unsigned int

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  Nn  é 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.

O tipo int

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_MAXINT_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 inti para o qual a diferença  Ni  é 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⌋.

Exercícios 2

  1. Sizeof.  Compile e execute o seguinte programa:
    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; }
    
  2. Escreva os números UINT_MAX, INT_MIN e INT_MAX em notação hexadecimal.
  3. Suponha que precisamos contar o número de ocorrências de um certo fenômeno num computador em que sizeof (unsigned) vale 2.  Sabemos de antemão que o fenômeno não ocorre mais que 65535 vezes.  Podemos usar uma variável do tipo unsigned para contar as ocorrências do fenômeno?  Que fazer se o fenômeno puder ocorrer mais que 65535 vezes? Proponha uma solução que use listas encadeadas para representar um contador em base 100.
  4. Suponha que sizeof (unsigned) vale 2 no seu computador. Qual o valor de 60000 + 30000 em aritmética unsigned int?  Qual o valor de 30000 + 15000 em aritmética int?  Qual o valor de 60000 × 11 em aritmética unsigned int?  Qual o valor de 30000 × 2 em aritmética int?
  5. Detecção de overflow.  Escreva uma função booleana que receba unsigned ints nm e decida (true ou false) se a adição de nm produz overflow.  Repita o exercício supondo que n e m são do tipo int.
  6. Até onde?  Escreva um programa que receba um unsigned int n e imprima as potências  n2, n3, n4, n5, etc.  O programa só deve parar quando não for capaz de armazenar a próxima potência em uma variável do tipo unsigned int.

Constantes

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 'Ã''ç' 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);

Exercícios 3

  1. Qual a diferença entre 'O', 'o', '0', '\0'0?
  2. Qual o efeito do seguinte fragmento de programa?
    for (int i = 1; i <= 26; ++i) 
       printf ("%c\n", 'a' + i - 1);
    

Expressões aritméticas que misturam tipos

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).

Exercícios 4

  1. Qual o efeito do seguinte fragmento de código?
    unsigned char u, v, w;
    u = 255; v = 2;
    printf ("%d", u + v); 
    w = u + v;
    printf ("%d", w); 
    
  2. Qual o efeito dos dois fragmentos de código a seguir?
    unsigned char u;
    for (u = 0; u < 256; ++u) 
       printf (".");
    
    char c;
    for (c = 0; c < 128; ++c) 
       printf (".");
    

Tipos inteiros grandes

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.)


Perguntas e respostas