0x3012h: Prohibición de aritmética de punteros sobre void*¶
Enunciado normativo¶
NO DEBE realizarse aritmética de punteros directamente sobre
void *. Para avanzar sobre memoria genérica, el puntero DEBE convertirse primero achar *,unsigned char *ouint8_t *.
¿Por qué existe esta regla?¶
El problema¶
La aritmética de punteros en C avanza en unidades del tipo apuntado: int *p; p + 1
avanza sizeof(int) bytes. Ese comportamiento depende de que el tamaño del tipo
sea conocido. Para void el tamaño no está definido: sizeof(void) es una
extensión de algunos compiladores que vale 1, no una garantía del estándar.
Por eso, en C estándar, void *p; p + 1 es inválido. GCC lo tolera como
extensión (avanza un byte) y emite advertencias sólo con banderas específicas.
La consecuencia es un código que compila en Linux/GCC pero no en otros
compiladores, y cuyo significado depende del entorno.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Portabilidad | No compila en compiladores que no aceptan la extensión. |
| Bug silencioso | El avance se interpreta como bytes o como elementos según el compilador. |
| Legibilidad | No queda claro cuál es la unidad de avance. |
| Mantenibilidad | Cada compilador exige banderas distintas para advertirlo. |
Fundamento en el estándar y en la cátedra¶
C11 §6.5.6 exige que el tipo apuntado tenga un tamaño definido para la aritmética
de punteros; void no lo tiene. C11 §6.2.5 completa el motivo: void es un tipo
incompleto que no puede completarse. La cátedra prohíbe la extensión y exige la
conversión a char *, la unidad de memoria por excelencia, como en
0x3011h: Si una función recibe un puntero genérico para operaciones de solo lectura, la firma de la función debe utilizar const void* para los buffers genéricos.
Alcance y excepciones¶
Aplica a void * y a punteros derivados (const void *). No aplica a
char * ni a unsigned char *, que tienen tamaño definido y son la alternativa
correcta. Tampoco prohíbe comparar punteros void * con NULL, ni convertirlos
con un cast; sólo prohíbe la suma o resta directa.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Avance directo sobre void *¶
void *siguiente = base + desplazamiento;
leer(siguiente);Por qué falla: base + desplazamiento es aritmética sobre void *, inválida en
C estándar. Bajo la extensión de GCC avanza desplazamiento bytes, pero otro
compilador puede rechazar la expresión o interpretarla distinto. El código gana
en brevedad lo que pierde en portabilidad.
❌ Contraejemplo 2 — sizeof(void) como unidad de tamaño¶
size_t bytes = cantidad * sizeof(void);
void *bloque = malloc(bytes);Por qué falla: sizeof(void) no está definido por el estándar; GCC lo trata como
1. La reserva queda dependiendo de una extensión y la intención (un arreglo de
bytes) no se expresa. Si se querían cantidad bytes, debe usarse
unsigned char y sizeof(unsigned char), o directamente cantidad.
✅ Ejemplo conforme 1 — Conversión a char * para avanzar¶
unsigned char *bytes = base;
unsigned char *siguiente = bytes + desplazamiento;
leer(siguiente);La conversión a unsigned char * fija la unidad de avance en bytes, portables
por definición (todo objeto tiene tamaño múltiplo de char). El cast explícito
deja visible la decisión de trabajar a nivel de bytes.
✅ Ejemplo conforme 2 — Recorrido de un buffer genérico de sólo lectura¶
size_t contar_ceros(const void *buffer, size_t n)
{
const unsigned char *bytes = buffer;
size_t ceros = 0;
for (size_t i = 0; i < n; i++)
{
if (bytes[i] == 0)
{
ceros++;
}
}
return ceros;
}El parámetro es const void * (conforme a 0x3011h: Si una función recibe un puntero genérico para operaciones de solo lectura, la firma de la función debe utilizar const void*) y el recorrido se
hace con indexación sobre const unsigned char *. No hay aritmética sobre
void * y el tipo de cada operación es claro.
⚠️ Casos límite¶
memcpyy compañía: la biblioteca ya hace la conversión internamente; no necesitás aritmética sobrevoid *para usarlas.Extensiones de compilador: aunque GCC acepte
void * + n, la cátedra la prohíbe porque el código debe ser C estándar.Alineación: convertir a
char *y volver a un tipo alineado requiere que la dirección siga alineada; documentalo.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Suma o resta directa sobre void *; uso de sizeof(void). |
gcc / clang | gcc -Wall -Wextra -Wpointer-arith -std=c11 ... | pointer of type 'void *' used in arithmetic. |
clang | clang -Weverything ... | arithmetic on a pointer to void is a GNU extension. |
Checklist de autocontrol¶
¿Evité sumar o restar sobre
void *?¿Convertí a
char *ounsigned char *antes de avanzar?¿Evité
sizeof(void)?¿La unidad de avance está explícita en el tipo?
¿El buffer genérico de lectura es
const void *?
Reglas relacionadas¶
0x3011h: Si una función recibe un puntero genérico para operaciones de solo lectura, la firma de la función debe utilizar const void* —
const void *para las operaciones de sólo lectura.0x300Ah: Utilizá cast explícito al convertir tipos de punteros — la conversión explícita entre tipos de puntero.
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — el tamaño se calcula con
sizeofde tipos completos.0x300Ch: Verificá siempre los límites de los arreglos antes de acceder a sus elementos — el avance sobre bytes también exige validar límites.