Regla 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*
Memoria, punteros y tipos (0x30XX)
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*¶
Enunciado normativo¶
Si una función recibe un puntero genérico
void *y NO modifica la memoria apuntada, el parámetro DEBE declararse comoconst void *. Se prohíbe pasarvoid *sin calificador cuando la operación es de sólo lectura.
¿Por qué existe esta regla?¶
El problema¶
void * es el puntero genérico: puede apuntar a cualquier tipo de objeto, pero
no se puede desreferenciar sin convertirlo. Es la herramienta natural para
funciones que operan sobre bytes o sobre datos de tipo desconocido, como
memcpy, memset, fwrite o un comparador de qsort.
Cuando esa función sólo lee, dejar el parámetro como void * niega el contrato.
La versión general de 0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique pide const en todo puntero de entrada;
const void * es la aplicación de esa regla al puntero genérico. Sin el
calificador, no se puede pasar un objeto declarado const, y el compilador no
puede avisar si la función escribe donde no debería.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Contrato débil | El llamador no sabe si sus datos serán alterados. |
| Restricción inversa | No se pueden pasar objetos const ni literales. |
| Bug silencioso | Una escritura accidental sobre datos de sólo lectura no se detecta. |
| Reutilización | La función no sirve para datos inmutables. |
Fundamento en el estándar y en la cátedra¶
C11 §6.7.3 define const; aplicado a void, protege el objeto apuntado sin
comprometer la genericidad. La biblioteca estándar es el modelo: memcmp y
memcpy usan const void * para el origen. La cátedra alinea la firma con ese
precedente, como especialización de 0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique.
Alcance y excepciones¶
Aplica a funciones genéricas de lectura: impresión, comparación, copia, cálculo
de hash, búsqueda. No aplica cuando la función escribe en el buffer (allí el
parámetro es void * sin const) ni cuando hay dos parámetros y sólo uno es de
lectura. Tampoco obliga a convertir datos concretos a void *: si el tipo es
conocido, un const T * es más informativo.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Impresión genérica sin const¶
void imprimir_hex(void *buffer, size_t n)
{
unsigned char *bytes = buffer;
for (size_t i = 0; i < n; i++)
{
printf("%02x ", bytes[i]);
}
}Por qué falla: la función sólo lee, pero el parámetro void * permite pasar
cualquier objeto y sugiere que podría modificarlo. Un llamador con datos const
no podrá usarla sin un cast que descarte el calificador, y el compilador no
protege el buffer.
❌ Contraejemplo 2 — Copia cuya fuente no es const¶
void copiar_bloque(void *destino, void *origen, size_t n)
{
for (size_t i = 0; i < n; i++)
{
((unsigned char *)destino)[i] = ((unsigned char *)origen)[i];
}
}Por qué falla: el origen es de sólo lectura, pero su parámetro no lo declara.
Además del contrato débil, la firma no permite pasar una fuente const y el
compilador no advierte si por error se escribe en origen.
✅ Ejemplo conforme 1 — Lectura con const void *¶
void imprimir_hex(const void *buffer, size_t n)
{
const unsigned char *bytes = buffer;
for (size_t i = 0; i < n; i++)
{
printf("%02x ", bytes[i]);
}
}El parámetro declara que buffer no se modifica. La conversión a
const unsigned char * conserva el calificador y permite recorrer los bytes. El
llamador puede pasar cualquier objeto, incluido uno const.
✅ Ejemplo conforme 2 — Origen const y destino mutable¶
void copiar_bloque(void *destino, const void *origen, size_t n)
{
unsigned char *salida = destino;
const unsigned char *entrada = origen;
for (size_t i = 0; i < n; i++)
{
salida[i] = entrada[i];
}
}La asimetría de la firma comunica el flujo: el destino se escribe, el origen se
lee. Es la misma forma que usa memcpy(restrict void *, const void *, size_t)
en la biblioteca estándar.
⚠️ Casos límite¶
Ambos parámetros de escritura:
void *sinconstes correcto; no apliques la regla mecánicamente.Conversión interna: al convertir
const void *aconst T *no se descarta el calificador; si el cast lo descarta, hay un error de diseño.Comparadores de
qsort: recibenconst void *; devolver un valor distinto de cero no debe modificar los objetos apuntados.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Parámetro void * sin const en función que sólo lee. |
gcc / clang | gcc -Wall -Wextra -Wcast-qual -std=c11 ... | cast discards 'const' qualifier al convertir el buffer. |
cppcheck | cppcheck --enable=style archivo.c | Parameter can be declared as const pointer. |
Checklist de autocontrol¶
¿La función modifica el buffer genérico?
Si no lo modifica, ¿el parámetro es
const void *?¿Conservé el
consten la conversión interna aconst T *?¿Los parámetros que sí escriben quedaron sin
const?¿Documenté el flujo de datos en la firma?
Reglas relacionadas¶
0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique — regla general de
consten parámetros de puntero.0x3012h: Prohibición de aritmética de punteros sobre void* — la aritmética sobre
void *requiere conversión explícita.0x300Ah: Utilizá cast explícito al convertir tipos de punteros — el cast explícito no debe descartar
const.