A conversão de tipos é um mecanismo fundamental na linguagem C que permite que valores de um tipo sejam tratados como outro tipo. Isso ocorre frequentemente em expressões, atribuições e passagens de argumentos. Compreender como e quando as conversões acontecem é essencial para escrever código correto e eficiente, evitando comportamentos inesperados e perda de dados.

Nesta aula, abordaremos os dois tipos principais de conversão: a implícita, realizada automaticamente pelo compilador, e a explícita, feita pelo programador através do operador de cast. Também veremos o conceito de promoção, que é uma forma especial de conversão implícita, e discutiremos as armadilhas mais comuns que podem levar a bugs sutis. Ao final, você será capaz de identificar e gerenciar conversões em seus programas C.

Conversão implícita

A conversão implícita (ou automática) ocorre quando o compilador converte um tipo de dado para outro sem instrução explícita do programador. Isso geralmente acontece em expressões que envolvem operandos de tipos diferentes, em atribuições onde o tipo do valor é diferente do tipo da variável alvo, e em passagens de argumentos para funções. O objetivo é evitar erros de tipo e permitir que a operação seja realizada.

As regras para conversão implícita seguem uma hierarquia de tipos: char e short são convertidos para int (promoção), e tipos mais baixos na hierarquia são convertidos para tipos mais altos para evitar perda de informação. Por exemplo, se um int é usado com um float, o int é convertido para float antes da operação. Da mesma forma, se um float é usado com um double, o float é convertido para double. No entanto, conversões que podem perder informação (como de float para int) geralmente geram um aviso do compilador, mas ainda são realizadas.

#include 

int main() {
    int a = 5;
    float b = 2.5;
    float resultado = a + b; // a é convertido implicitamente para float
    printf("Resultado: %f\n", resultado);
    
    int x = 10;
    double y = x; // conversão implícita de int para double
    printf("y = %f\n", y);
    
    return 0;
}

Casting explícito

O casting explícito permite que o programador force a conversão de um valor para outro tipo, usando a sintaxe (tipo) expressão. Isso é útil quando se deseja realizar uma conversão que o compilador não faria automaticamente, ou para deixar claro que a conversão é intencional. O casting explícito também pode ser usado para evitar avisos do compilador em conversões potencialmente perigosas.

Por exemplo, ao dividir dois inteiros, o resultado é truncado. Para obter um resultado de ponto flutuante, podemos converter explicitamente um dos operandos para float ou double. Outro uso comum é quando precisamos armazenar um valor de um tipo maior em um tipo menor, sabendo que pode haver perda de dados. O casting explícito torna a intenção do programador clara e evita avisos do compilador.

#include 

int main() {
    int a = 7, b = 2;
    float divisao = (float) a / b; // casting explícito de a para float
    printf("Divisão: %f\n", divisao);
    
    double pi = 3.14159;
    int aproximado = (int) pi; // casting explícito de double para int (trunca)
    printf("Aproximado: %d\n", aproximado);
    
    return 0;
}

Promoção

Promoção é um tipo específico de conversão implícita que ocorre quando um tipo inteiro menor que int (como char, short, ou _Bool) é usado em uma expressão. O compilador promove (converte) esses tipos para int (ou unsigned int) antes de realizar a operação. Isso é feito por razões históricas e de eficiência, já que a maioria das arquiteturas processa inteiros de forma mais rápida.

Por exemplo, ao somar dois chars, eles são primeiro promovidos a int, a soma é realizada, e o resultado é um int. Se o resultado for atribuído de volta a um char, pode ocorrer truncamento. A promoção também se aplica a operadores unários como + e -, e a argumentos de funções variádicas (como printf). É importante estar ciente da promoção para evitar surpresas, especialmente em operações com tipos menores.

#include 

int main() {
    char c1 = 100, c2 = 100;
    int soma = c1 + c2; // c1 e c2 são promovidos a int, soma = 200
    printf("Soma: %d\n", soma);
    
    char c3 = 200; // 200 cabe em char? Depende (signed char máximo 127)
    // O valor pode ser negativo, mas a promoção para int preserva o valor
    int valor = c3;
    printf("Valor: %d\n", valor); // pode imprimir -56 se char for signed
    
    return 0;
}

Armadilhas

Conversões de tipo podem levar a comportamentos inesperados se não forem compreendidas corretamente. Uma armadilha comum é a perda de dados ao converter de um tipo maior para um menor, como de int para char. Outra é a conversão de float para int, que trunca a parte fracionária em vez de arredondar. Além disso, a promoção pode causar resultados inesperados em expressões com tipos mistos, especialmente com unsigned e signed.

Por exemplo, ao comparar um int com um unsigned int, o int é convertido para unsigned, o que pode levar a resultados surpreendentes se o int for negativo. Outro problema é o uso de operadores bitwise com tipos promovidos, que podem gerar resultados com tamanho diferente do esperado. Para evitar armadilhas, é recomendável usar casting explícito quando a conversão não é óbvia, e estar atento aos avisos do compilador.

#include 

int main() {
    // Armadilha: comparação entre signed e unsigned
    int a = -1;
    unsigned int b = 1;
    if (a < b) {
        printf("a é menor que b\n"); // Isto não será impresso!
    } else {
        printf("a é maior ou igual a b\n"); // a é convertido para unsigned, vira um número grande
    }
    
    // Armadilha: truncamento em atribuição
    int grande = 1000;
    char pequeno = grande; // perda de dados: 1000 não cabe em char
    printf("pequeno: %d\n", pequeno); // resultado imprevisível
    
    return 0;
}

Boas práticas

Para evitar problemas com conversões de tipo, siga estas recomendações:

  • Use casting explícito quando a conversão não for óbvia ou quando quiser evitar avisos do compilador.
  • Evite misturar tipos signed e unsigned em comparações.
  • Prefira tipos com sinal (int) para valores que podem ser negativos.
  • Esteja ciente da promoção de tipos e do truncamento em atribuições.
  • Compile com warnings habilitados (-Wall -Wextra) e corrija todos os avisos.

Referências

Exercícios

  1. Escreva um programa que declare duas variáveis int e uma variável float. Atribua valores e realize a divisão do primeiro int pelo segundo, armazenando o resultado em float. Use casting explícito para obter o resultado correto. Imprima o resultado.
  2. ✓ Resposta:
    #include 
    
    int main() {
        int a = 10, b = 3;
        float resultado = (float) a / b;
        printf("Resultado: %f\n", resultado);
        return 0;
    }
  3. Explique o que acontece quando você atribui um valor do tipo double a uma variável do tipo int. Dê um exemplo.
  4. ✓ Resposta: A atribuição de um double a um int causa uma conversão implícita que trunca a parte fracionária (não arredonda). Por exemplo:
    double d = 3.99;
    int i = d; // i vale 3
  5. Considere o código: char c = 200; int i = c; printf("%d", i); O que será impresso se char for signed? E se for unsigned? Justifique.
  6. ✓ Resposta: Se char for signed (faixa -128 a 127), 200 está fora da faixa, então o valor é convertido para -56 (200 - 256) e i recebe -56. Se char for unsigned (faixa 0 a 255), 200 é armazenado normalmente e i recebe 200. A promoção para int preserva o valor, mas o sinal depende da interpretação do char.
  7. Crie um programa que demonstre a armadilha de comparar um int negativo com um unsigned int. Mostre o resultado inesperado.
  8. ✓ Resposta:
    #include 
    
    int main() {
        int a = -1;
        unsigned int b = 1;
        if (a < b) {
            printf("a é menor\n");
        } else {
            printf("a é maior ou igual\n"); // Isto é impresso, pois a é convertido para unsigned grande
        }
        return 0;
    }
  9. Escreva um programa que use casting explícito para converter um double em int e depois em char. Imprima os valores e observe a perda de dados.
  10. ✓ Resposta:
    #include 
    
    int main() {
        double d = 1234.567;
        int i = (int) d; // i = 1234
        char c = (char) i; // c = -46 (pois 1234 % 256 = 210, mas char signed pode ser negativo)
        printf("double: %f\n", d);
        printf("int: %d\n", i);
        printf("char: %d\n", c);
        return 0;
    }