Disponible la versión 6 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.2.1e Puntero constante/ a constante

§1 Sinopsis

Un puntero puede ser declarado con el modificador const; recuérdese que cualquier entidad declarada con esta propiedad no puede modificar su valor ( 3.2.1c). En el caso de la declaración de punteros, la posición del modificador es determinante, de forma que pueden darse dos situaciones cuyos resultados son bastante distintos.

§2 Puntero-a-constante

Las dos formas que siguen son análogas; en ambos casos representan puntero-a-tipoX-constante (el objeto al que se apunta es constante), abreviadamente: puntero-a-constante. Se utilizan para avisar al compilador que el objeto referenciado no puede ser modificado, ni aún a través de su puntero.

tipoX const * puntero ... ;
const tipoX * puntero ... ;

Ejemplo:

int const * xPtr = &x;
const int * yPtr = &y;

xPtr e yPtr son dos objetos del mismo tipo (puntero-a-int constante). Ambos señalan a sendos enteros cuyo Rvalue no pueden ser modificado, sin embargo, los punteros si pueden ser modificados. Por ejemplo, señalando a un nuevo objeto (siempre naturalmente que este sea un int constante).


Los punteros-a-constante son de utilización más frecuente que los punteros constantes (ver a continuación). Se utilizan principalmente cuando se pasan punteros a funciones, porque se desea aligerar la secuencia de llamada ( 4.4.6b). En especial si los punteros señalan a objetos muy grandes y se desea evitar que la función invocada pueda modificar el objeto en cuestión.

Ejemplo:

class CMG {...};       // clase muy grande
void fun(const CMG*);  // función aceptando un puntero a la clase
 
int main() {           // ===========
  CMG* aptr = new CMG;
  ...
  fun(aptr);           // fun no puede modificar el objeto
}

§3 Puntero constante

La expresión que sigue significa puntero-constante-a-tipoX (el puntero es constante), abreviadamente: puntero constante. Se utiliza para informar al compilador que el valor del puntero no puede cambiar, aunque el objeto al que apunta si pueda cambiar (si no es constante).

tipoX * const puntero ... ;

Puesto que en este tipo de sentencias el puntero es declarado constante y su valor no puede ser modificado con posterioridad, hay que establecer su valor en el mismo momento de la declaración. Es decir, debe ser una definición del tipo:

tipoX * const puntero = dirección-de-objeto ;

Ejemplo:

int* const xPtr = &x;

En este caso xPtr es un puntero constante-a-int. El puntero es constante, lo que significa que su Rvalue no puede ser cambiado; por tanto debe señalar al entero x durante toda su vida.


§4 No confundir puntero constante con puntero-a-constante. Como puede verse en la última línea del ejemplo que sigue, ambos casos pueden darse simultáneamente (puntero constante a constante):

int nN = 7;                  // entero iniciado a 7
const int nC = 7;            // entero constante iniciado a 7
int *const pt1 = &nN;        // puntero constante a int
const int * pt2 = &nC;       // puntero a constante tipo int
const int * const pt3 = &nC  // puntero constante a constante tipo int

Como resumen podemos afirmar que:

  • Un puntero constante no puede ser modificado
  • Un puntero-a-constante apunta a un objeto cuyo Rvalue no puede cambiar


Observe que, desde la óptica del C++, puntero-a-tipoX y puntero-a-tipoX-constante son tipos distintos, del mismo modo que tipoX y tipoX-constante son también tipos distintos. Ejemplos:

const int kte = 75;
int x = 75;
int* ptr;           // puntero-a-int
ptr = &kte;         // Error. La asignación no es posible
const int* ptk;     // puntero-a-int-constante
ptk = &kte;         // Ok: Asignación correcta


§5 Es ilegal crear un puntero que pudiera violar la no asignabilidad de un objeto constante. Considere los ejemplos siguientes:

int i;                  // i es int (no iniciado)
const int ci = 7;       // ci es int constante (iniciado a 7)
i = ci;                 // Ok: Asigna constante-int a int (i == 7)
ci = 0;                 // ERROR: asignar valor a constante
ci--;                   // ERROR: modificar valor de constante
int * pi;               // pi es puntero-a-int (no iniciado)
int * const cp = &i;    // cp es puntero-constante-a-int (iniciado), apunta a un int no constante (i)
*cp = ci;               // Ok: Asigna constante-int al objeto apuntado por un puntero-constante-a-int (el objeto apuntado i, no es constante)
cp = &ci;               // ERROR: asignar nuevo valor a puntero constante
const int * pci = &ci;  // pci es puntero-a-constante-int
*pci = 3;               // ERROR: asignar valor al objeto apuntado por pci (es puntero-a-constante)
++pci;                  // Ok: Incrementa puntero-a-constante-int
const int * const cpc = &ci; // cpc es puntero-constante-a-constante-int
pci = cpc;              // Ok: Asigna puntero-constante-a-constante-int a un puntero-a-constante-int
cpc++;                  // ERROR: modificar valor de puntero-constante
pi = pci;               // ERROR: si se permitiese la asignación, sería posible asignar al valor apuntado por pci (una constante) mediante asignación a la dirección apuntada por pi.


Reglas similares pueden aplicarse al modificador volatile. Observe que ambos: const ( 3.2.1c ) y volatile ( 4.1.9) pueden aparecer como modificadores del mismo identificador.


§5.1  A pesar a pesar de lo indicado anteriormente, es posible modificar una constante, de forma indirecta, a través de un puntero. Considere el siguiente ejemplo [2]:

int const edad = 40;    // L1: constante tipo int iniciada a 40
printf("%3i\n", edad);  // L2: -> 40
*(int *)&edad = 35;     // L3: Se acepta la instrucción sin error
printf("%3i\n", edad);  // L4: -> 40
printf("%3i\n", *(int *)&edad);  // L5: -> 35

Comentario:

El ejemplo, un tanto sorprendente, merece una explicación:

L1:  La variable edad se define como constante-tipo-int. Se inicia al valor 40

L2:  Se muestra su valor: 40 como cabría esperar.

L3: Esta sentencia se ejecuta sin error. Modifica el valor de la constante de forma indirecta, a través de un puntero, asignándole un valor de 35.

La explicación es que la dirección &edad, Lvalue(edad), que es la dirección de una constante-tipo-int, es modificada por el operador de modelado (int *) que la promueve a puntero-a-int (aquí se pierde el carácter de constante); a este puntero lo denominaremos provisionalmente iptr. Finalmente, la expresión *iptr = 35 asigna 35 a la "variable" señalada por iptr, lo que es perfectamente legal.

L4:  Esta sorprendente sentencia indica que edad sigue teniendo el valor 40 (su valor inicial).

L5:  Acto seguido, pedimos que nos muestre el valor guardado en la dirección iptr (la dirección de edad), y nos muestra el valor modificado, 35.

La explicación de esta aparente contradicción es que (por razones de eficacia en la ejecución del código), en la línea 4 el compilador utiliza el valor de la constante resuelta en tiempo de compilación (se le había dicho -Línea 1- que era constante). El compilador supone que será realmente constante y en el segmento de código correspondiente a la sentencia de impresión pone directamente el valor 40 obtenido en tiempo de compilación. Este es el valor que nos muestra en la línea 4. Si embargo, cuando en la línea 5 se le pide que muestre el verdadero valor (el que hay en la zona de almacenamiento de variables [1]) muestra el valor modificado.

  Inicio.


[1] Recuérdese lo indicado al respecto ( 3.2.3); no está garantizado que el compilador asigne un Lvalue a los objetos declarados inicialmente como constantes.

[2] Naturalmente que todos estos "trucos" están totalmente desaconsejados, y no está garantizado que funcionen en todos los compiladores. El C++ ofrece alternativas adecuadas y portables para hacer las cosas.