Const-correctness y qué promete realmente const
const es una promesa sobre escrituras a través de un camino de acceso particular.
const int *p significa "no modifiques el int a través de p"; no significa que el
objeto sea globalmente inmutable, poseído por el callee, vivo para siempre o thread-safe.
int * const p significa que el objeto puntero en sí no puede reseatearse.
Const-correctness es la disciplina de poner esas promesas en fronteras de API para que el
código read-only no pueda volverse write-capable por accidente.
El reset:
constse pega a un nivel de indirección. Preguntá "qué objeto está protegido: lo apuntado, la variable puntero o ambos?"
Leé la declaración por niveles
Los casos comunes:
| Declaración | ¿Puede reseatear el puntero? | ¿Puede escribir a través de él? |
|---|---|---|
int *p |
sí | sí |
const int *p |
sí | no |
int const *p |
sí | no |
int * const p |
no | sí |
const int * const p |
no | no |
const int * e int const * son el mismo tipo. Vuelven read-only al int apuntado a
través de ese puntero. int * const fija el objeto puntero pero todavía te deja modificar
el int al que apunta.
Para punteros multinivel, cada nivel importa:
const char **a; // puede cambiar *a; no puede escribir **a
char * const *b; // no puede cambiar *b; puede escribir **b
const char * const *c; // no puede cambiar *c ni escribir **c
Acá el lenguaje vago tipo "puntero const" falla. Nombrá el nivel.
Cómo funciona realmente
const es un type qualifier chequeado por el compilador. Puede prevenir escrituras
accidentales:
int sum(const int *items, size_t count);
Esa firma dice que sum no va a mutar los elementos a través de items. Puede aceptar
arrays mutables y arrays const porque un objeto mutable puede verse a través de un puntero
read-only. Lo inverso no es seguro: un puntero a datos const no debe volverse
write-capable silenciosamente.
La promesa es local al camino de acceso. En este programa, el objeto cambia aunque exista una vista const:
int x = 1;
const int *view = &x;
x = 2; // legal: acceso mutable directo
Eso no es una contradicción. view prometió no escribir; no congeló x.
Los objetos declarados const son más fuertes:
const int limit = 10;
Escribir a limit a través de un puntero casteado es undefined behavior. Los compiladores
pueden ubicar objetos const en memoria read-only o asumir que no cambian. Castear const
solo es válido si el objeto original no fue declarado const y estás restaurando un camino
mutable que poseés legítimamente.
const tampoco maneja lifetime. Un const char * puede quedar dangling. Tampoco vuelve
thread-safe a datos compartidos; dos threads pueden racear sobre un objeto mutable aunque
una API lo vea a través de un puntero const.
Artefacto ejecutable: const en distintos niveles
El demo vive en examples/pointers-and-memory/const-correctness-and-what-const-really-promises/demo.c.
// demo.c — muestra la diferencia entre puntero a dato `const`, puntero `const`
// y APIs que prometen leer sin mutar.
// Compila limpio y corre con:
//
// gcc -O0 -Wall -Wextra demo.c -o demo && ./demo
#include <stddef.h>
#include <stdio.h>
static int sum_read_only(const int *items, size_t count) {
int sum = 0;
for (size_t i = 0; i < count; i++) {
sum += items[i];
}
return sum;
}
static void reseat_pointer(int **slot, int *target) {
*slot = target;
}
static void print_name(const char *name) {
printf("name = %s\n", name);
}
int main(void) {
int a = 10;
int b = 20;
int values[] = {1, 2, 3, 4};
const int *read_only_view = &a;
int *const fixed_pointer = &a;
int *moving_pointer = &a;
printf("read through const = %d\n", *read_only_view);
read_only_view = &b;
printf("reseated const view = %d\n", *read_only_view);
*fixed_pointer = 11;
printf("wrote via fixed ptr = %d\n", a);
reseat_pointer(&moving_pointer, &b);
*moving_pointer = 21;
printf("b after reseat write = %d\n", b);
printf("sum read-only array = %d\n",
sum_read_only(values, sizeof values / sizeof values[0]));
print_name("low-level atlas");
return 0;
}
Compilá y corré:
gcc -O0 -Wall -Wextra demo.c -o demo
./demo
Salida real:
read through const = 10
reseated const view = 20
wrote via fixed ptr = 11
b after reseat write = 21
sum read-only array = 10
name = low-level atlas
La vista const int * puede reseatearse de a a b, pero no puede escribir a través de
esa vista. El puntero int * const no puede reseatearse, pero todavía puede escribir en
a. La API de suma read-only acepta un array mutable común porque el acceso read-only es
una restricción segura.
Modos de falla y trade-offs
- Pensar que
constsignifica inmutable. Normalmente significa "no a través de esta expresión." Otros aliases todavía pueden mutar un objeto no-const. - Castear un const real. Si el objeto original fue declarado
const, escribir a través de un puntero casteado es undefined behavior. - Olvidar lifetime.
const char *ppuede apuntar a un string literal, un buffer de stack, una allocation de heap o storage muerto. Const no dice nada sobre lifetime. - Sorpresas de const superficial.
const struct Table *tpreviene escrituras a campos a través det, pero cualquier campo puntero interno puede apuntar a objetos mutables si sus tipos no están también const-qualified. - Mismatch de qualifiers en APIs. Una función que solo lee pero acepta
T *rechaza callers const y anuncia capacidad de escritura que no necesita. - Const como teatro de cleanup. Repartir
consten scopes locales mínimos vale menos que hacer read-only correctamente las fronteras de API.
En la práctica
- Poné
consten buffers de input. Preferívoid parse(const char *text)eint sum(const int *items, size_t count)para acceso read-only. - Devolvé
const T *para vistas prestadas read-only. Dice que callers pueden inspeccionar pero no mutar a través de ese puntero. - Usá
T * constcon moderación. Puede aclarar invariantes locales, pero la constness de API suele importar más. - No casteés
constpara satisfacer una mala API. Arreglá la API cuando puedas; si no, aislá y documentá la frontera insegura. - Propagá const en datos anidados deliberadamente. Si un grafo, tabla o array de strings debería ser read-only a través de una API, calificá también las capas apuntadas.
Conecta con: Qué es realmente un puntero · Punteros a punteros · void* y type erasure · Punteros y Memoria · C desde el Metal
Fuentes
- ISO/IEC 9899 (working drafts del estándar C en WG14) — type qualifiers, reglas de constraints, acceso por lvalue y undefined behavior después de modificar objetos const. https://www.open-std.org/jtc1/sc22/wg14/
- cppreference —
consttype qualifier — referencia compacta para ubicación de qualifiers, niveles de puntero y constraints. https://en.cppreference.com/w/c/language/const - cppreference — Pointer declaration — sintaxis de declarators de puntero y ejemplos de const multinivel. https://en.cppreference.com/w/c/language/pointer
- Jens Gustedt — Modern C — const-correctness práctica a nivel API y disciplina de qualification de punteros. https://gustedt.gitlabpages.inria.fr/modern-c/
- Robert Seacord — Effective C — guía de C seguro sobre interfaces const-correct, casts y evitar mutación accidental. https://nostarch.com/Effective_C