Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x300Ah: Utilizá cast explícito al convertir tipos de punteros

Memoria, punteros y tipos (0x30XX)

Universidad Nacional de Río Negro

0x300Ah: Utilizá cast explícito al convertir tipos de punteros

Enunciado normativo

Toda conversión entre tipos de puntero DEBE escribirse con un cast explícito. NO DEBE agregarse cast cuando la conversión es implícita y segura, en particular sobre el retorno de malloc, calloc o realloc.

¿Por qué existe esta regla?

El problema

C es permisivo con las conversiones de punteros, pero no todas son equivalentes. Convertir un void * a cualquier T * es implícito y seguro: void * es el puntero genérico y el asignador lo devuelve así. Convertir entre T1 * y T2 * con tipos incompatibles no es un ajuste de tipo, sino una reinterpretación del contenido, y desreferenciar el resultado viola las reglas de aliasing.

El cast explícito hace visible cuál de las dos cosas está ocurriendo. Sin él, el lector no distingue una conversión inocua de una peligrosa, y el compilador puede pasar por alto diagnósticos útiles. La regla exige ser explícito donde la conversión es real, y no agregar ruido donde no lo es.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Bug silenciosoUn cast oculta tipos incompatibles y el error aparece en ejecución.
Comportamiento indefinidoDesreferenciar el resultado viola strict aliasing (C11 §6.5 párr. 7).
PortabilidadLos casts sobre malloc esconden la falta de #include <stdlib.h>.
LegibilidadNo se distingue conversión intencional de error de tipos.

Fundamento en el estándar y en la cátedra

C11 §6.5.4 define el operador de conversión. C11 §6.3.2.3 permite convertir implícitamente entre un puntero de objeto y void * (ida y vuelta). La cátedra pide la forma explícita sólo para conversiones entre tipos de puntero distintos de void *, y prohíbe el cast redundante en la reserva para que el compilador delate la ausencia de la cabecera (ver 0x300Ah: Utilizá cast explícito al convertir tipos de punteros). Es el punto de equilibrio entre claridad y seguridad.

Alcance y excepciones

Aplica a conversiones explícitas entre T1 * y T2 *, entre punteros y enteros, y entre punteros a función. Excepción: la conversión implícita void *T * no requiere ni admite cast; escribir int *p = (int *)malloc(n * sizeof(*p)); agrega un cast redundante que la cátedra desaconseja. Tampoco se requiere cast al pasar T * a un parámetro void *.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Conversión implícita entre punteros incompatibles

unsigned char *bytes = obtener_buffer();
int *valores = bytes;

Por qué falla: int * y unsigned char * son tipos de objeto distintos; la asignación es una violación de restricción que el compilador advierte con -Wincompatible-pointer-types. Al desreferenciar valores, el acceso a un objeto de tipo int a través de un lvalue de otro tipo (o viceversa) viola las reglas de aliasing. Si la conversión es deliberada (reinterpretar bytes), debe ser explícita y documentada.

❌ Contraejemplo 2 — Cast redundante que esconde una cabecera faltante

#include <stdio.h>
double *datos = (double *)malloc(10 * sizeof(*datos));

Por qué falla: falta #include <stdlib.h>, de modo que malloc se declara implícitamente como función que retorna int. Sin el cast, el compilador avisa de la conversión de int a puntero; con el cast, el aviso desaparece y en plataformas de 64 bits la dirección puede truncarse. El cast tapa el diagnóstico en lugar de resolver la causa.

✅ Ejemplo conforme 1 — Conversión explícita deliberada

unsigned char *bytes = obtener_buffer();
int *valores = (int *)bytes;

El cast deja claro que se está reinterpretando la memoria. Quien lo lea sabrá que el acceso a *valores sólo es válido si los bytes están correctamente alineados y contienen un int; ambas condiciones deben documentarse. La conversión es visible y auditable.

✅ Ejemplo conforme 2 — Reserva sin cast redundante

int *valores = malloc(10 * sizeof(*valores));
if (valores == NULL)
{
    return -1;
}

malloc devuelve void *, que se convierte implícitamente a int *. No hace falta cast y su ausencia permite que el compilador detecte un stdlib.h faltante. La verificación de 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica queda en la línea siguiente.

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cConversión implícita entre tipos de puntero; cast redundante sobre malloc/free.
gcc / clanggcc -Wall -Wextra -std=c11 -Wcast-qual -Wstrict-aliasing ...incompatible-pointer-types, cast discards const, strict-aliasing.
cppcheckcppcheck --enable=all archivo.cC-style pointer casting sin justificación.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Casteo redundante de malloc()

Síntoma en el código del estudiante

Toda reserva viene precedida por un molde de tipo sobre malloc, calloc o realloc:

int *p = (int *)malloc(sizeof(int) * 10);
nodo_t *n = (nodo_t *)malloc(sizeof(nodo_t));

El casteo se copia por inercia, muchas veces heredado de ejemplos de C++ o de materiales antiguos.

Diagnóstico

Mecanismo del defecto

En C, void * se convierte implícitamente a cualquier puntero a objeto y viceversa (§6.3.2.3p1). El cast no agrega ninguna conversión necesaria: repite la que el compilador ya haría. Peor aún, si falta #include <stdlib.h>, C89 y C99 asumen que malloc retorna int; el cast explícito silencia la advertencia del compilador y en una arquitectura de 64 bits trunca la dirección a 32 bits, corrompiendo el puntero.

Es decir: el cast no solo es redundante, es activamente peligroso porque tapa el síntoma (la declaración implícita) que debería delatar un #include faltante.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
int *p = (int *)malloc(sizeof(int) * 10);
if (p == NULL) {
    return NULL;
}
✅ Código refactorizado
int *p = malloc(10 * sizeof(*p));
if (p == NULL) {
    return NULL;
}
✅ Cast que sí corresponde — conversión entre tipos incompatibles
void *generico = obtener_bloque();
nodo_t *n = (nodo_t *)generico;

Acá el cast documenta una conversión real entre tipos de objeto distintos y es el caso que la regla 0x300Ah sí exige.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

# Sin <stdlib.h> y con cast, el compilador no avisa:
$ gcc -Wall -Wextra -std=c11 programa.c
programa.c:5:14: warning: implicit declaration of function 'malloc'
 # el cast (int *) silencia el aviso en compiladores permisivos

$ ./programa
# posible truncamiento en 64 bits: puntero inválido
Segmentation fault (core dumped)

Checklist de verificación

Reglas relacionadas

Antipatrón: Casteo redundante en invocación de free()

Síntoma en el código del estudiante

La llamada a free lleva un molde a void *:

free((void *)ptr);

A veces el cast se escribe a un tipo concreto ((int *), (nodo_t *)) y otras a void *. En cualquier caso, es un molde sobre un argumento que ya se convierte solo.

Diagnóstico

Mecanismo del defecto

free recibe un void *. Al pasarle un puntero a cualquier tipo de objeto, el compilador aplica la conversión implícita de §6.3.2.3p1: no hace falta cast. El molde, entonces, no convierte nada: solo agrega ruido y sugiere una operación que no existe.

Hay un matiz de estilo además: la regla 0x300Ah exige cast explícito para conversiones reales entre tipos de puntero incompatibles. Aquí no hay tal conversión, porque el destino es void *. Escribir free((int *)ptr) tampoco aporta: free vuelve a convertir a void * inmediatamente.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
free((void *)ptr);
free((nodo_t *)nodo);
✅ Código refactorizado
free(ptr);
ptr = NULL;

free(nodo);
nodo = NULL;

Sin cast y con anulación posterior del puntero, según la regla 0x3002h.

✅ Cast legítimo de otra operación
void *bloque = malloc(sizeof(nodo_t));
nodo_t *n = (nodo_t *)bloque;   /* conversión real de void* a tipo concreto */

Nótese la diferencia: acá el cast documenta el tipo con el que se va a usar el bloque, no un adorno alrededor de free.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

# El cast a void* no genera ninguna advertencia ni error;
# el problema es de estilo:
$ gcc -Wall -Wextra -std=c11 programa.c
# sin diagnóstico por el cast innecesario

Checklist de verificación

Reglas relacionadas

Antipatrón: Casteo forzado entre punteros de tipos incompatibles (Violación de Strict Aliasing)

Síntoma en el código del estudiante

Se toma la dirección de un objeto y se reinterpreta con un puntero a un tipo distinto, para “ver los bits”:

float f = 1.0f;
int *pi = (int *)&f;
printf("%d\n", *pi);

El cast fuerza la lectura de un float como si fuera int.

Diagnóstico

Mecanismo del defecto

C11 §6.5p7 fija qué tipos pueden acceder a un objeto almacenado: el tipo efectivo del objeto, versiones calificadas, un tipo compatible, un agregado que lo contenga, o un tipo de carácter. Un float accedido a través de un int * no cumple ninguna de esas condiciones: es comportamiento indefinido (violación de strict aliasing).

El compilador asume que punteros a tipos incompatibles no apuntan a la misma memoria. Con optimizaciones, puede reordenar una escritura y una lectura que el programador creía dependientes, o descartar una relectura por considerarla invariante. El resultado cambia según el nivel de optimización.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
float f = 1.0f;
int *pi = (int *)&f;
int bits = *pi;   /* violación de strict aliasing */
✅ Código refactorizado con memcpy
#include <string.h>

float f = 1.0f;
int bits;
memcpy(&bits, &f, sizeof(bits));

memcpy copia la representación de bytes sin aliasing: es la operación explícitamente bendecida por el estándar.

✅ Alternativa con union
union {
    float como_float;
    int como_int;
} u;

u.como_float = 1.0f;
int bits = u.como_int;

La union comparte almacenamiento y la reinterpretación queda contenida en un tipo agregador.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

$ gcc -O0 -std=c11 programa.c && ./programa
1065353216

$ gcc -O2 -std=c11 programa.c && ./programa
0

$ gcc -O2 -Wstrict-aliasing=2 -std=c11 programa.c
programa.c:3:16: warning: dereferencing type-punned pointer will break
 strict-aliasing rules

Checklist de verificación

Reglas relacionadas

Antipatrón: Casteo de retorno de malloc() con omisión de include stdlib.h

Síntoma en el código del estudiante

Falta #include <stdlib.h> y el cast explícito “arregla” el aviso:

int *p = (int *)malloc(sizeof(int));

El archivo no incluye la cabecera, pero el cast hace que el código compile sin reclamos.

Diagnóstico

Mecanismo del defecto

Sin prototipo de malloc, el compilador antiguo asume la regla de C89: una función no declarada retorna int. Es una declaración implícita, que en C99/C11 es además una restricción diagnóstica. El cast (int *) convierte ese int presumido en un puntero, y así silencia la advertencia que delataría la falta de #include.

El problema es de tamaño: si el compilador asume un retorno int de 32 bits y la dirección real tiene 64 bits, los 32 bits altos se descartan. El puntero resultante es una dirección truncada que, al desreferenciar, apunta a cualquier parte. El comportamiento es indefinido y la portabilidad nula.

En C99/C11 el compilador debe avisar igual por la llamada implícita; pero muchos entornos con configuración laxa lo toleran, y el cast impide ver el problema subyacente.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
/* falta #include <stdlib.h> */
int *p = (int *)malloc(sizeof(int));
*p = 42;
✅ Código refactorizado
#include <stdlib.h>

int *p = malloc(sizeof(*p));
if (p == NULL) {
    return NULL;
}
*p = 42;

Incluir la cabecera y quitar el cast resuelve las dos caras del mismo error.

✅ Ejemplo conforme adicional — cabeceras completas
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *p = malloc(10 * sizeof(*p));
    if (p == NULL) {
        return 1;
    }
    free(p);
    p = NULL;
    return 0;
}

Las funciones de biblioteca usadas tienen su cabecera: malloc y free (<stdlib.h>), printf (<stdio.h>) y size_t (<stddef.h>).

⚠️ Casos límite

Errores típicos al compilar o ejecutar

$ gcc -std=c11 -pedantic-errors programa.c
programa.c: In function 'main':
programa.c:3:14: error: implicit declaration of function 'malloc'
 [-Wimplicit-function-declaration]

# En compilador permisivo:
programa.c:3:14: warning: implicit declaration of function 'malloc'
$ ./programa
Segmentation fault (core dumped)

Checklist de verificación

Reglas relacionadas

Antipatrón: Casteo forzado de tipos numéricos o literales enteros a punteros

Síntoma en el código del estudiante

Se fabrica un puntero a partir de un entero literal:

int *p = (int *)0x1000;
*p = 42;

El número pretende ser una dirección de memoria “conocida”, tomada de un ejemplo, de una dirección de hardware o de la imaginación.

Diagnóstico

Mecanismo del defecto

Un entero y un puntero pertenecen a dominios distintos. Convertir un entero a puntero es posible con cast, pero el estándar lo declara implementation-defined: cada plataforma decide qué significa, y el resultado puede no ser una dirección válida ni conservar el valor numérico.

El problema se materializa al desreferenciar. En un sistema con memoria virtual, la dirección 0x1000 (4096) rara vez está mapeada para el proceso. El acceso genera una falla de página y SIGSEGV. Elegir “de memoria” 0x1000 es especialmente desafortunado: cae en la primera página, deliberadamente no mapeada para detectar desreferencias nulas.

Escribir en una dirección fija solo tiene sentido en código de muy bajo nivel —un arranque de kernel, un registro mapeado en memoria— con documentación y mapeo explícito. Fuera de ese contexto es un error de portabilidad y una caída segura.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
int *p = (int *)0x1000;
*p = 42;
✅ Código refactorizado — obtener la dirección de un objeto real
int valor = 0;
int *p = &valor;
*p = 42;
✅ Memoria dinámica
int *p = malloc(sizeof(*p));
if (p == NULL) {
    return NULL;
}
*p = 42;
free(p);
p = NULL;
✅ Puntero nulo cuando corresponde
int *p = NULL;   /* no (int *)0 */

NULL expresa “sin objeto”; un literal como 0x1000 expresa una dirección concreta que probablemente no exista.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

$ gcc -Wall -Wextra -std=c11 programa.c
programa.c:1:9: warning: cast to pointer from integer of different size
 [-Wint-to-pointer-cast]
    1 | int *p = (int *)0x1000;

$ ./programa
Segmentation fault (core dumped)

Checklist de verificación

Reglas relacionadas