O C converte valores entre tipos o tempo todo. Algumas dessas conversões você escreve com um cast; a maioria o compilador realiza em silêncio, sob regras que você não escolheu. Saber qual é qual é a diferença entre "por que a minha média sempre dá 3?" e um código que faz o que diz.
Conversão implícita
Sempre que um valor de um tipo encontra um contexto que espera outro, o C converte:
Conversões que não podem perder informação (int para double, char para int, short para long) são alargamentos e são sempre seguras. Conversões na direção contrária são estreitamentos e podem perder dados - o 3.9 acima virou 3 sem nenhum aviso, a menos que você peça um com -Wconversion.
As conversões aritméticas usuais
Quando um operador binário tem operandos de tipos diferentes, o C os converte para um tipo comum antes de fazer o trabalho. A escada, de baixo para cima:
- Qualquer coisa menor que
int(char,short,_Bool) é promovida aint. Isso é a promoção de inteiros e acontece primeiro, sempre. - Se um dos lados for
long double, o outro viralong double. - Senão, se um for
double, o outro viradouble. - Senão, se um for
float, o outro virafloat. - Senão, entre tipos inteiros, o de maior posto vence, e se os postos empatarem, o sem sinal vence.
Essa última regra é a que causa bugs de verdade. O resto é intuitivo.
A promoção de inteiros é a razão de a aritmética com char não transbordar do jeito que você poderia esperar - e de guardar o resultado de volta num char transbordar.
Casts explícitos
Um cast é o tipo de destino entre parênteses:
(double)x
(int)3.9
(char)65
(unsigned int)n
Ele se aplica à expressão imediatamente seguinte, e liga muito forte - mais forte que *, / ou +.
A primeira linha divide como inteiros (dando 3) e depois converte 3 para 3.0 - tarde demais. A segunda converte total antes da divisão, então o operador / vê um double e um int, promove o int e realiza divisão em ponto flutuante.
Converter um dos operandos basta. As conversões aritméticas usuais cuidam do outro.
Corrigindo a divisão inteira
Essa é, de longe, a razão mais comum para escrever um cast em C:
A linha da porcentagem é instrutiva: passed / n é 3 / 5, que dá 0 como inteiros, e 0 * 100 é 0. Multiplicar antes de dividir (100 * passed / n) corrige isso mesmo sem um cast, porque 300 / 5 é exato - mas isso só funciona quando os números colaboram. O cast é a correção confiável.
Truncamento, não arredondamento
Converter um valor de ponto flutuante para inteiro descarta a fração. Ele trunca em direção a zero - não arredonda:
Se você compilar isso na sua própria máquina, lembre-se de que math.h precisa de -lm na ligação no Linux.
Mais um perigo: converter um valor de ponto flutuante grande demais para o tipo inteiro é comportamento indefinido, e não um giro. (int)1e20 pode produzir qualquer coisa. Confira o intervalo antes do cast quando o valor não estiver sob o seu controle.
char e int
Um char em C é um inteiro pequeno guardando um código de caractere. Converter entre os dois é trabalho do dia a dia:
digit - '0' é o idioma padrão para transformar um caractere de dígito no seu valor, e funciona porque os dez caracteres de dígito têm garantia de serem consecutivos. Para letras, prefira toupper() e tolower() de ctype.h à aritmética do + 32 - o deslocamento é um fato do ASCII, não uma garantia do C.
Uma armadilha relacionada: funções de ctype.h como isdigit e toupper recebem um int que precisa ser EOF ou representável como unsigned char. Passar um char puro que seja negativo (possível, já que char puro pode ter sinal) é indefinido. Converta: isdigit((unsigned char)c).
A armadilha signed/unsigned
O passo 5 da escada de conversões - o sem sinal vence o empate - produz a comparação mais surpreendente do C:
-1 é convertido para unsigned int, o que reinterpreta o seu padrão de bits como 4.294.967.295. Isso é maior que 1, então a comparação é falsa.
A mesma conversão faz laços rodarem para sempre:
/* BUG: i e sem sinal, entao i >= 0 e sempre verdadeiro. Quando i e 0, i-- da a volta. */
for (size_t i = n - 1; i >= 0; i--) { ... }
E faz verificações de comprimento falharem:
/* BUG: strlen devolve size_t (sem sinal). Se a string tiver menos de 5,
len - 5 da a volta para um numero enorme e o teste passa. */
if (strlen(s) - 5 > 0) { ... }
Reescreva como if (strlen(s) > 5) e a subtração nunca acontece.
As defesas: mantenha contagens e índices com a mesma sinalização do começo ao fim, compile com -Wsign-compare (incluído em -Wextra) e, quando precisar misturar, converta explicitamente depois de estabelecer que o valor não pode ser negativo.
Convertendo ponteiros
Casts também convertem entre tipos de ponteiro, e aí eles carregam risco real porque mudam como a memória é interpretada, não os bytes em si.
Numa máquina little-endian isso imprime 01 00 00 00. Inspecionar a representação de um objeto através de um unsigned char * é um dos poucos casts de ponteiro que a norma abençoa explicitamente.
A maior parte dos outros casts de ponteiro não é abençoada. Ler um int através de um float * viola a regra de aliasing estrito e é comportamento indefinido mesmo compilando; use memcpy para reinterpretar bytes.
Duas convenções que vale conhecer. void * converte de e para qualquer tipo de ponteiro de objeto sem cast em C, e é por isso que você não deve converter o resultado do malloc:
int *arr = malloc(n * sizeof *arr); /* C correto */
int *arr = (int *)malloc(n * sizeof *arr); /* desnecessario; esconde um cabecalho faltando */
O cast é obrigatório em C++, e é por isso que tanto código o tem. Em C ele pode esconder o erro de esquecer o <stdlib.h>.
E printf("%p", ...) espera um void *, então argumentos de ponteiro ali realmente precisam de um cast: printf("%p", (void *)p).
Quando um cast é a resposta errada
Um cast cala o compilador. Às vezes o compilador estava certo.
long big = 5000000000L;
int small = (int)big; /* o cast esconde perda real de dados */
Se o valor de fato cabe, o cast documenta que você conferiu. Se pode não caber, o cast converteu um aviso numa resposta errada silenciosa. Antes de escrever um, pergunte se a correção não é mudar o tipo de uma variável - double em vez de int, size_t em vez de int, long long em vez de long. Um cast é a ferramenta certa principalmente quando dois tipos corretos precisam se encontrar para uma operação, como em (double)sum / count.
Perguntas frequentes
Como se faz um cast em C?
Coloque o tipo de destino entre parênteses antes do valor: (double)x, (int)3.9, (char)65. O cast se aplica à expressão imediatamente seguinte, então (double)a / b converte a primeiro e depois divide, enquanto (double)(a / b) divide como inteiros e converte o resultado.
Como converter um int em float em C?
A atribuição faz isso implicitamente - double d = 5; guarda 5.0. Dentro de uma expressão você muitas vezes precisa de um cast explícito: (double)total / count força a divisão em ponto flutuante em vez da divisão inteira.
O que acontece quando você converte um float em int em C?
A parte fracionária é descartada - truncada em direção a zero, nunca arredondada. (int)3.9 é 3 e (int)-3.9 é -3. Para arredondar, some 0.5 antes do cast no caso de positivos, ou use round(), floor() ou ceil() de math.h.
Por que comparar um int com sinal e um sem sinal dá a resposta errada?
As conversões aritméticas usuais do C convertem o valor com sinal para sem sinal, então -1 < 1u é falso: -1 vira um número positivo enorme. Mantenha contagens e tamanhos com a mesma sinalização, ou converta explicitamente depois de checar que o valor não pode ser negativo.