Post

Infdev #04 - Tipos de C e como eles funcionam.

Infdev #04 - Tipos de C e como eles funcionam.

Como dito no primeiro post (Infdev #01 - C é uma linguagem com problemas, e porquê eles são importantes), o grande diferencial de C foi a existência de diferentes tipos com diferentes tamanhos. Porém, ao mesmo tempo, como C é uma linguagem feita para rodar em qualquer sistema, não existe uma definição rígida de tamanho para cada tipo em C: um int pode ter 16 bits ou 32 bits, dependendo unicamente da arquitetura da sua máquina. Cada arquitetura terá sua própria definição de tamanhos para os tipos em C. O máximo que temos como padrão é um tamanho mínimo para cada tipo. Neste post, considerando nossas necessidades, vamos trabalhar apenas com máquinas de 64 bits.

Tipos:

C possui 4 tipos para inteiros: char, short, int e long. Todos os tipos para inteiros podem ser com ou sem sinal. Por padrão, eles possuem sinal, porém podem ser declarados sem sinal utilizando unsigned. Temos 3 tipos para ponto flutuante: float, double e long double. E 1 tipo para strings, porém esse tipo é compartilhado com inteiros: o char.

Char

Vamos explicar primeiro sobre o char, que é um tipo que pode ser mais difícil de entender por ter mais de um uso. Um char é um tipo que armazena valores entre 0 e 255, ou seja, equivale a um inteiro de 8 bits. O char pode ser utilizado como um inteiro de 8 bits caso você precise, porém seu uso principal é representar um caractere (daí o nome char, de character) em UTF-8. Talvez você já tenha ouvido falar que uma string é uma lista de caracteres em outras linguagens, porém isso é abstraído; em C, as strings são declaradas exatamente como uma lista de char.

1
2
3
4
int main() {
    char hello[] = "Hello World";
    printf("%s\n", hello);
}

Short; Int; Long

De forma simples, em uma máquina de 64 bits:

  • short é um inteiro de 16 bits
  • int é um inteiro de 32 bits
  • long é um inteiro de 64 bits

Isso completa o grupinho de int8 (char), int16 (short), int32 (int) e int64 (long). Ainda existe um tipo para inteiros de 128 bits, o __int128, porém esse é bem mais específico e depende da versão de C que você está utilizando e do seu compilador. Então, caso tenha curiosidade ou necessidade de inteiros maiores que 64 bits, saiba que, com sorte, é possível sem o uso de bibliotecas de terceiros.

Float; Double; Long Double

O float implementa o padrão IEEE 754. Você não precisa saber como isso funciona, apenas precisa saber que é um número de ponto flutuante de precisão única (o mais comum em qualquer linguagem) e que tem 32 bits de tamanho. Caso queira precisão dupla, utilize double com 64 bits; e, se precisar de precisão estendida, pode usar long double com 128 bits.

stdint.h

Todos esses fatores tornam o uso de tipos numéricos um tanto complicado em C, especialmente se você não tem familiaridade com a linguagem. E eu ainda resumi bastante o assunto, pois cada tipo possui variantes de declaração para cobrir diferentes arquiteturas e plataformas, como mostra a tabela resumida abaixo:

Ninguém gosta de utilizar tipos dessa forma, e por isso foi criada a biblioteca stdint.h – uma biblioteca que disponibiliza várias macros (farei um post falando sobre macros, mas por enquanto considere como um segundo nome para cada tipo) para dar nomes menos ambíguos aos tipos de inteiros. A stdint.h possui vários tipos mais óbvios em relação ao tamanho, tanto para inteiros com sinal quanto sem sinal, como int8_t, int16_t, uint32_t etc. Eu recomendo que, sempre que for utilizar números, em vez de usar os tipos padrão, utilize os tipos da stdint.h para facilitar a leitura do seu código.

1
2
3
4
5
6
7
8
9
10
11
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int64_t v1 = 90;
    long v2 = 192;

    printf("Tamanho de int64_t: %ld\n", sizeof(int64_t)); // 8 bytes (64 bits)
    printf("Tamanho de long: %ld\n", sizeof(long));       // 8 bytes (64 bits)
}

Overflow e Underflow

Quero aproveitar o contexto de tipos e tamanhos de inteiros para falar sobre underflow e overflow, pois eu não acredito que teriam conteúdo para um post único, mas ainda é um tópico importantes. Algo que você já deve ter ouvido sobre são os problemas de overflow e underflow. Em C, não existem verificações automáticas para evitar overflow ou underflow, a responsabilidade é totalmente sua. Se você não sabe o que é overflow/underflow, lembre-se: um tipo de 32 bits ocupa 32 bits na memória, ou convertendo, 8 bytes, começando no menor valor possível (no caso de um int32_t com sinal, -2.147.483.648, e sem sinal, 0), com o binário 00000000 00000000 00000000 00000000, indo até o valor máximo (para int32_t com sinal, 2.147.483.647, e sem sinal, 4.294.967.295), com o binário 11111111 11111111 11111111 11111111. Porém, se somarmos 1 ao valor máximo, ocorre um overflow, e o valor volta ao mínimo. Esse problema é famoso por causar bugs, o que era muito comum em jogos antigos, em máquinas de 8 ou 16 bits, onde o valor máximo era muito menor que hoje em dia, tornando mais fácil provocar um overflow. Underflow é exatamente o mesmo que overflow, porém no sentido negativo: se você subtrair 1 do valor mínimo, ocorre um underflow e o valor salta para o máximo possível. O mesmo conceito vale para tipos de ponto flutuante.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdint.h>

int main() {
    uint8_t valor1 = 255; // Maximo de 8 bits sem sinal
    valor1 += 1;
    printf("%d\n", valor1); // 0

    int32_t valor2 = 2147483647; // Maximo de 32 bits com sinal
    valor2 += 1;
    printf("%d\n", valor2); // -2147483648
}
This post is licensed under CC BY 4.0 by the author.

Trending Tags