Menu

Conversión de tipos en C: conversiones implícitas y casts explícitos

C convierte entre tipos constantemente, a veces porque lo pediste con un cast y más a menudo por su cuenta. Aquí están las reglas de promoción, la sintaxis del cast explícito y las conversiones que pierden datos en silencio.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

C convierte valores entre tipos constantemente. Algunas de esas conversiones las escribes tú con un cast; la mayoría las realiza el compilador en silencio bajo reglas que no elegiste. Saber cuál es cuál es la diferencia entre "¿por qué mi promedio siempre da 3?" y código que hace lo que dice.

Conversión implícita

Siempre que un valor de un tipo llega a un contexto que espera otro, C lo convierte:

Las conversiones que no pueden perder información (int a double, char a int, short a long) son de ampliación y siempre son seguras. Las conversiones en el otro sentido son de estrechamiento y pueden perder datos: el 3.9 de arriba se volvió 3 sin ningún aviso, a menos que lo pidas con -Wconversion.

Las conversiones aritméticas habituales

Cuando un operador binario tiene operandos de tipos distintos, C los convierte a un tipo común antes de hacer el trabajo. La escalera, de abajo hacia arriba:

  1. Cualquier cosa más pequeña que int (char, short, _Bool) se promociona a int. Esto es la promoción de enteros y ocurre primero, siempre.
  2. Si cualquiera de los lados es long double, el otro pasa a long double.
  3. Si no, si cualquiera es double, el otro pasa a double.
  4. Si no, si cualquiera es float, el otro pasa a float.
  5. Si no, entre tipos enteros gana el de mayor rango, y si los rangos empatan, gana el sin signo.

Esa última regla es la que causa errores de verdad. El resto son intuitivas.

La promoción de enteros es por lo que la aritmética con char no se desborda como podrías esperar, y por lo que guardar el resultado de vuelta en un char sí lo hace.

Casts explícitos

Un cast es el tipo destino entre paréntesis:

(double)x
(int)3.9
(char)65
(unsigned int)n

Se aplica a la expresión que va inmediatamente después, y se asocia muy fuerte, más que *, / o +.

La primera línea divide como enteros (dando 3) y luego convierte el 3 a 3.0: demasiado tarde. La segunda convierte total antes de la división, así que el operador / ve un double y un int, promociona el int y realiza división en punto flotante.

Convertir un operando basta. Las conversiones aritméticas habituales se ocupan del otro.

Arreglar la división entera

Esta es con diferencia la razón más común para escribir un cast en C:

La línea del porcentaje es instructiva: passed / n es 3 / 5, que como enteros es 0, y 0 * 100 es 0. Multiplicar antes de dividir (100 * passed / n) lo arregla incluso sin un cast, porque 300 / 5 es exacto, pero eso solo funciona cuando los números cooperan. El cast es el arreglo fiable.

Truncamiento, no redondeo

Convertir un valor de punto flotante a un entero descarta la fracción. Trunca hacia cero: no redondea.

Si compilas esto en tu propia máquina, recuerda que math.h necesita -lm al enlazar en Linux.

Un peligro más: convertir un valor de punto flotante demasiado grande para el tipo entero es comportamiento indefinido, no una vuelta a la carga. (int)1e20 puede producir cualquier cosa. Comprueba el rango antes de convertir cuando el valor no esté bajo tu control.

char e int

Un char en C es un entero pequeño que guarda un código de carácter. Convertir entre los dos es trabajo de todos los días:

digit - '0' es el modismo estándar para convertir un carácter de dígito en su valor, y funciona porque los diez caracteres de dígito tienen garantizado ser consecutivos. Para las letras, prefiere toupper() y tolower() de ctype.h a la aritmética con + 32: el desplazamiento es un hecho de ASCII, no una garantía de C.

Una trampa emparentada: las funciones de ctype.h como isdigit y toupper toman un int que debe ser o bien EOF o bien representable como unsigned char. Pasar un char a secas que sea negativo (posible, ya que un char simple puede tener signo) es indefinido. Conviértelo: isdigit((unsigned char)c).

La trampa de signed y unsigned

El paso 5 de la escalera de conversiones —el sin signo gana el empate— produce la comparación más sorprendente de C:

-1 se convierte a unsigned int, lo que reinterpreta su patrón de bits como 4.294.967.295. Eso es mayor que 1, así que la comparación es falsa.

La misma conversión hace que los bucles corran para siempre:

/* BUG: i es sin signo, asi que i >= 0 siempre es verdadero. Cuando i es 0, i-- da la vuelta. */
for (size_t i = n - 1; i >= 0; i--) { ... }

Y hace fallar las comprobaciones de longitud:

/* BUG: strlen devuelve size_t (sin signo). Si la cadena es mas corta
   que 5, len - 5 da la vuelta a un numero enorme y la prueba pasa. */
if (strlen(s) - 5 > 0) { ... }

Reescríbelo como if (strlen(s) > 5) y la resta nunca ocurre.

Las defensas: mantén los conteos y los índices con un solo tipo de signo en todo el código, compila con -Wsign-compare (incluida en -Wextra), y cuando debas mezclar, convierte explícitamente tras establecer que el valor no puede ser negativo.

Convertir punteros

Los casts también convierten entre tipos de puntero, y aquí conllevan un riesgo real porque cambian cómo se interpreta la memoria, no los bytes en sí.

En una máquina little-endian esto imprime 01 00 00 00. Inspeccionar la representación de un objeto a través de un unsigned char * es una de las pocas conversiones de punteros que el estándar bendice explícitamente.

La mayoría de las demás conversiones de punteros no están bendecidas. Leer un int a través de un float * viola la regla de aliasing estricto y es comportamiento indefinido aunque compile; usa memcpy para reinterpretar bytes.

Dos convenciones que conviene conocer. void * se convierte desde y hacia cualquier tipo de puntero a objeto sin un cast en C, y por eso no deberías convertir el resultado de malloc:

int *arr = malloc(n * sizeof *arr);        /* C correcto */
int *arr = (int *)malloc(n * sizeof *arr); /* innecesario; oculta una cabecera olvidada */

El cast es obligatorio en C++, que es por lo que tanto código lo lleva. En C puede esconder el error de olvidar <stdlib.h>.

Y printf("%p", ...) espera un void *, así que los argumentos de tipo puntero ahí sí necesitan un cast de verdad: printf("%p", (void *)p).

Cuándo un cast es la respuesta equivocada

Un cast silencia al compilador. A veces el compilador tenía razón.

long big = 5000000000L;
int  small = (int)big;    /* el cast esconde una perdida real de datos */

Si el valor de verdad cabe, el cast documenta que lo comprobaste. Si puede que no quepa, el cast ha convertido un aviso en una respuesta equivocada silenciosa. Antes de escribir uno, pregúntate si el arreglo es cambiar el tipo de una variable: double en vez de int, size_t en vez de int, long long en vez de long. Un cast es la herramienta correcta sobre todo cuando dos tipos correctos deben encontrarse para una operación, como en (double)sum / count.

Preguntas frecuentes

¿Cómo se hace un cast en C?

Pon el tipo destino entre paréntesis delante del valor: (double)x, (int)3.9, (char)65. El cast se aplica a la expresión que va inmediatamente después, así que (double)a / b convierte a primero y luego divide, mientras que (double)(a / b) divide como enteros y convierte el resultado.

¿Cómo convierto un int a float en C?

La asignación lo hace de forma implícita: double d = 5; guarda 5.0. Dentro de una expresión a menudo necesitas un cast explícito: (double)total / count fuerza la división en punto flotante en lugar de la división entera.

¿Qué pasa cuando conviertes un float a int en C?

La parte fraccionaria se descarta: se trunca hacia cero, nunca se redondea. (int)3.9 es 3 y (int)-3.9 es -3. Para redondear, suma 0.5 antes del cast en los positivos, o usa round(), floor() o ceil() de math.h.

¿Por qué comparar un int con signo y uno sin signo da la respuesta equivocada?

Las conversiones aritméticas habituales de C convierten el valor con signo a sin signo, así que -1 < 1u es falso: -1 se vuelve un número positivo enorme. Mantén los conteos y los tamaños con un solo tipo de signo, o convierte explícitamente tras comprobar que el valor no puede ser negativo.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR