Está en la página 1de 11

.

2 Punteros
1 Presentacin
Al hablar de punteros es inevitablemente hablar de la memoria, as que permitidme una pequea introduccin
al tema.
A efectos del programador, la memoria RAM puede suponerse como una sucesin de contenedores capaces
de albergar datos (podramos imaginarlos como una sucesin de vagones de tren). Estos contenedores tienen
dos atributos: direccin y contenido.

Direccin: un identificativo que sirve para distinguirlos. Para esto es suficiente un nmero entero
positivo progresivamente creciente desde la posicin ms baja, la direccin 0 (que correspondera al
primer vagn), a la posicin ms alta XXXXX (que correspondera al ltimo). Es tradicin informtica
que estos nmeros se representen enhexadecimal (
2.2.4b), de forma que las direcciones se
suelen representar como xxxxxh en los textos de programacin.
Los ordenadores modernos utilizan un modelo de memoria denominado "plano" ("Flat memory
model") en el cual la memoria se presenta como un todo continuo desde el punto ms bajo hasta el
mximo soportado por el hardware (mximo que puede direccionar). En la prctica la RAM instalada
en un equipo es siempre inferior a este valor mximo permitido por su arquitectura [3], pero el
mecanismo de memoria virtual (
exista espacio en disco.

H5.1) puede simular la existencia de RAM adicional mientras

Contenido: el contenido correspondiente a cada direccin est siempre en binario (la memoria fsica
solo puede contener este tipo de variables
0.1), y la capacidad de cada vagn depende de la
plataforma. En la arquitectura PC, el espacio sealado por cada direccin puede contener un byte
(octeto). Como esto es muy poco (solo los tipos char caben en un octeto) para representar datos se
utilizan vagones sucesivos en nmero suficiente. Por ejemplo, si ordenamos al compilador que traiga
un dato que est en la direccin xxxxh y es un int. El compilador ya sabe que tiene que traer el
contenido de esa celda y las tres siguientes (

2.2.4).

Como corolario de lo anterior, recordemos que al hablar de la "direccin de un objeto" nos referimos siempre
a la direccin donde comienza su almacenamiento. Por contra, la expresin "direccin de memoria" se
refiere a un vagn especfico.
Como veremos a continuacin, los punteros son un tipo de dato que sirven para almacenar direcciones de
memoria, y su importancia radica en que la programacin de mquinas de Von Newmann tal como las
conocemos hoy (

2), gira en torno al concepto de direccin de memoria.

2 Sinopsis
Los punteros son un tipo especial de datos C++ cuyo fin especfico es almacenar direcciones de objetos.
Comparten las caractersticas de las variables. Es decir: tienen un valor (tienen Rvalue); pueden ser
asignados (tienen Lvalue); tienen un lgebra especfica (se pueden realizar con ellos determinadas
operaciones); pueden ser almacenados en matrices; pasados como parmetros a funciones y devueltos por
estas. Comprenden dos categoras principales: punteros-a-objeto y punteros-a-funcin segn el tipo de
objeto sealado. Ambas categoras comparten ciertas operaciones, pero tienen uso, propiedades y reglas de
manipulacin distintas.

Nota: ms que un "tipo" deberamos decir mejor una "familia de tipos". Tendremos ocasin de ver que
existen tantos sub-tipos de puntero como tipos de objetos distintos pueden ser sealados por ellos.
Tambin veremos que cuando se declara un puntero, se especifica a que "tipo" de objeto puede sealar;
en el futuro se limitar a contener direcciones de objetos de ese tipo.

3 Generalidades
El tratamiento de punteros en C++ utiliza su propio vocabulario con el que es aconsejable familiarizarse desde
el principio. En general decimos coloquialmente que un puntero "apunta" o "seala" a un objeto determinado
cuando su valor (del puntero) es la direccin del objeto (direccin de memoria donde comienza su
almacenamiento). El objeto sealado por el puntero se denominareferente. Por esta razn tambin se dice
que el puntero "referencia" al objeto. El operador que permite obtener la direccin de un objeto (para asignarlo
a un puntero) se denomina operador de "referencia" (
del puntero se denomina "deferenciar" (

4.9.11), y la operacin de obtener el referente a partir

4.9.11).

Como veremos a lo largo de este captulo, los punteros representan magnitudes escalares (nmeros) con casi
todas las propiedades de los enteros sin signo, pero tienen sus propias reglas y restricciones para asignacin,
conversin, y aritmtica (disponen de ciertos operadores aritmticos). Por supuesto, su tamao es
el suficiente para almacenar una direccin de memoria en la arquitectura de la computadora utilizada.
Nota: en la mayora de compiladores de 32 bits los punteros ocupan 4 bytes, es decir, coincide con el de
un entero, de forma que sizeof(int) == sizeof(void*) no obstatante, no tiene porqu ser as
necesariamene. Por ejemplo, en algunos compiladores para 64 bits, el tamao del puntero es mayor que
el tamao de un entero.
En cualquier caso, el tamao de un puntero Ptr puede comprobarse mediante el operador sizeof (
y una sentencia del tipo:

4.9.13)

cout << sizeof Ptr << endl;


Considere el siguiente ejemplo:
char * ptc = "A";
int entero = 100;
int * pti = &entero;
printf("Tamao de puntero a carcter:%4d bytes\n", sizeof(ptc) );
printf("Tamao de puntero a entero:%4d bytes\n", sizeof(pti) );

Salida (compilador C++Builder de Borland para Windows 32):


Tamao de puntero a carcter: 4 bytes
Tamao de puntero a entero: 4 bytes

4 En el ordenador digital todo est almacenado en memoria y/o en los registros del procesador. Incluso los
datos almacenados en dispositivos externos deben ser volcados a memoria para su utilizacin. Habida cuenta
que esta memoria es manejada por el procesador y su circuitera asociada (chipset) por medio de direcciones,
parece evidente que los punteros son un tipo de dato especial e importante, tanto en C++ como en casi
cualquier lenguaje de programacin [1].

Los registros del procesador reciban un tratamiento especial para su acceso (distinto del de la memoria), lo
que explica que los punteros no sirvan para contener las direcciones de tales registros, y que el operador de
referencia & (
4.9.11) tampoco pueda ser aplicado para obtener su direccin (de los registros). Como
consecuencia directa de lo anterior, los punteros tampoco pueden ser utilizados para almacenar las
direcciones de valores devueltos por funciones, dado que estos valores se almacenan en los registros.
Los punteros son un recurso que en cierta forma podra considerarse de muy bajo nivel, ya que permiten
manipular directamente contenidos de memoria. Por esta razn, su utilizacin puede presentar algunos
problemas y exigen que se preste una especial atencin a aquellas secciones del cdigo que los utilizan.
Recuerde que los errores ms insidiosos y difciles de depurar que se presentan en los programas C++, estn
relacionados con el uso descuidado de punteros. Precisamente por razn del comentario de K&R apuntado al
principio y porque son frecuentemente origen de errores, algunos lenguajes prefieren evitarlos por completo,
al menos su utilizacin explcita, y en otros casos su utilizacin est muy restringida [2]. En cambio el lenguaje
ensamblador est plagado de ellos y desde luego, en C++, que no se distingue precisamente por soslayar los
peligros, constituyen un captulo extraordinariamente importante.

Como eplogo de lo anterior y aclaracin para el estudiante, en especial los que no se han enfrentado antes a
estos conceptos, o los hayan utilizado indirectamente (en lenguajes que los encapsulan en un envoltorio ms
amable), digamos que la utilizacin de punteros exige un cierto entrenamiento mental, ya que en principio son
difciles de "ver" [4], en especial los casos en que se utilizan punteros-a-punteros y que cuesta un cierto
esfuerzo distinguir fluidamente entre valor del puntero, el valor del objeto sealado por este valor, y el
significado de las operaciones asociadas (referencia y deferencia). Si este es su caso "Don't panic", sepa que
es bastante normal y que basta un poco de prctica para que estos conceptos fluyan rpidamente y sin
problema.

DIRECCIN
Un identificativo
Que sirve para
Distinguirlos
MEMORIA RAM

COMPARTIMIENTOS
MEMORIA

DE

CONTENIDO
El contenido correspondiente a cada
direccin est siempre en binario (la
memoria fsica solo puede contener
este tipo de variables

4.2.2 Aritmtica de punteros


1 Sinopsis
La aritmtica de punteros se limita a suma, resta, comparacin y asignacin. Las operaciones
aritmticas en los punteros de tipoX (punteros-a-tipoX) tienen automticamente en cuenta el
tamao real de tipoX. Es decir, el nmero de bytes necesario para almacenar un objeto tipoX [2].
Por ejemplo, suponiendo una matriz de double con 100 elementos, si ptr es un puntero a dicha
matriz, la sentencia ptr++; supone incrementar el Rvalue de ptr en 6.400 bits, porque el tamao
de la matriz es precisamente 100x64 bits.
Nota: no confundir el puntero-a-matriz con un puntero a su primer elemento (que aqu sera
puntero-a-double).
La aritmtica realizada internamente en los punteros depende del modelo de memoria en uso y de
la presencia de cualquier modificador superpuesto.
Las operaciones que implican dos punteros exigen que sean del mismo tipo o se realice
previamente un modelado apropiado
.

2 Operaciones permitidas
Sean ptr1, ptr2 punteros a objetos del mismo tipo, y n un tipo entero o una enumeracin; las
operaciones permitidas y los resultados obtenidos con ellas son:

Operacin Resultado

Comentario

pt1++

puntero

Desplazamiento ascendente de 1
elemento(desplazamiento de una direccion)

pt1--

puntero

Desplazamiento descendente de 1 elemento

pt1 + n

puntero

Desplazamiento ascendente n elementos [4]

pt1 - n

puntero

Desplazamiento descendente n elementos [4]

pt1 - pt2

entero

Distancia entre elementos

pt1 ==
NULL

booleano

Siempre se puede comprobar la igualdad o


desigualdad con NULL

pt1 !=
NULL

booleano

pt1 <R>
pt2

booleano

<R> es una expresin relacional (

pt1 = pt2

puntero

Asignacin

pt1 = void

puntero
genrico

Asignacin

3.2.1b)
4.9.12)

La comparacin de punteros solo tiene sentido entre punteros a elementos de la misma matriz;
en estas condiciones los operadores relacionales : ==, !=, <, >, <=, >=, funcionan correctamente.

Hemos sealado que cuando se realizan operaciones aritmticas con punteros, se tiene en cuenta
el tamao de los objetos apuntados, de modo que si un puntero es declarado apuntando-a-tipoX,
aadirle un entero n (al puntero) supone hacerlo hace avanzar un nmero n de objetos tipoX.
Si tipoX tiene un tamao de 10 bytes, aadir 5 al puntero-a-tipoX lo hace avanzar 50 bytes en
memoria (si se trata de punteros a elementos de una matriz, supone avanzar n elementos en la
matriz [3]).
Del mismo modo, la diferencia entre dos punteros resulta ser el nmero de objetos tipoX que
separa a dos punteros-a-tipoX. Por ejemplo, si ptr1 apunta al tercer elemento de una matriz,
y ptr2 apunta al dcimo elemento, el resultado ptr2-ptr1 es 7 (en realidad, la diferencia de dos
punteros solo tiene sentido cuando ambos apuntan a la misma matriz).
observe que no est definida la suma entre punteros.
Si ptr es un puntero a un elemento de una matriz, desde luego no existe un elemento tal como:
"uno despus del ltimo", pero se permite que ptr tenga dicho valor. Si ptr1 apunta al ltimo
elemento del array, ptr+1 es legal, pero ptr+2 es indefinido (lo que a efectos prcticos significa
que devolver basura, o un runtime, volcado de memoria, etc).
Si ptr apunta a uno despus del ltimo, ptr-1 es legal (puntero al ltimo elemento). Sin
embargo, aplicando el operador de indireccin * a un puntero despus del ltimo conduce a una
indefinicin.
Informalmente puede pensarse en ptr + n como avanzar el puntero en (n * sizeof(tipoX)) bytes,
siempre que ptr se mantenga en su rango legal (entre el primer elemento y uno despus del
ltimo).
La resta de dos punteros a elementos de la misma matriz, ptr1-ptr2, produce un entero n del
tipo ptrdiff_t definido en <stddef.h> Este nmero representa la diferencia entre los
subndices i y j de los dos elementos referenciados (n = i-j). Para esto es necesario
que ptr1 y ptr2 apunten a elementos existentes, o uno despus del ltimo.

4 Ejemplos:
N. Expresin Resultado
1. ip+10;

Produce otro puntero; Si ip es un puntero al elemento m[j] de una matriz (dicho


de otro modo: si *ip == &m[j] ), el nuevo puntero apunta a otro

elemento m[j+10] de la misma matriz, con lo que *(ip+10) == &m[j+10]


2. y =
*ip+10;

Aade 10 al objeto *ip (objeto referenciado por ip) y lo asigna a y

3. *ip +=
1;

Equivale a: *ip = *ip + 1. Incrementa en 1 el valor (Rvalue) del objeto


referenciado por ip.

4. ++*ip;

Igual que el anterior: incrementa en 1 el valor del objeto referenciado por ip

5. ++ip;

El resultado es otro puntero. Equivale a ip = ip+1, es decir, incrementa en 1 el


valor del puntero, con lo que el nuevo puntero seala a otra posicin (ver caso 1.)

6. ip++;

Igual que el caso anterior. Incrementa en 1 el valor del puntero.

7. (*ip)++

Igual que el caso 4. Dado que el parntesis tiene mxima precedencia y asocia de
izquierda a derecha ( 4.9.0a), incrementa en 1 el valor del objeto referenciado
por ip.
Observe que el parntesis es necesario; sin l la expresin modifica la posicin de
memoria sealado porip. Es decir, se realiza ip++;. Si ip es un puntero al
elemento m[j] de una matriz, el resultado es un puntero al elemento m[j+1].
Despus se tomara la indireccin, es decir, el resultado final sera el valor del
elemento m[j+1].
Por tanto, la expresin *ip++ equivale a *(ip++)

8. *ip+1;

El valor resultante es el de incrementar en 1 el valor del objeto apuntado por ip

5 Ejemplos
Consideremos copiar una cadena NTBS ( 3.2.3f) definida por un puntero a su origen s, en un
destino definido por un punterop. Considerando que el final de la cadena est sealado por el
carcter nulo, el proceso podra ser:
while (*s != 0) {
*p = *s;
// copiar carcter
s++ ;
p++ ;
}
Teniendo en cuenta que estamos usando el postincremento (
4.9.1) para s y p, y que cualquier
<expresin> es cierta si <expresin> != 0. Lo anterior sera equivalente a:
while (*s ) {
*p = *s ;
s++;
p++;
}
El incremento se puede expresar en la misma sentencia de asignacin:

while (*s ) { *p++ = *s++ ; }


La razn es que *p++ indica el carcter apuntado por p antes que sea incrementado. El
postincremento ++ no cambia p hasta que el carcter ha sido actualizado (primero se efecta la
asignacin y despus se incrementa). El orden de ejecucin de las operaciones involucradas es:
1.- Se asigna *p

*s

2.- Se incrementa s
3.- Se incrementa p
La comparacin puede hacerse en el mismo momento que la copia de caracteres:
while ( *p++ = *s++)
;

// 5.1

La razn es que despus de la ltima asignacin que correspondera al carcter '\0' de s, el


resultado de la asignacin sera justamente este valor (0 == falso) y se saldra del while.
Por la razn inversa, la expresin *++p indica el carcter apuntado por p despus que ha sido
incrementado. Ambos tipos de expresiones de asignacin con punteros, simultaneadas con
pre/post incrementos/decrementos, son muy frecuentes en los bucles que involucran punteros. De
hecho, las expresiones:
*p++ = val;
val = *--p;

// cargar la pila con val


// sacar de la pila el ltimo valor, asignarlo a val

son las sentencias estndar de cargar/descargar valores val de una pila LIFO [1] manejada por un
puntero p.
Nota: esta capacidad de C++ para permitir formas de cdigo tan extraordinariamente
compactas, tiene fervientes defensores y acrrimos detractores. En mi opinin, el nico
problema es que sin un entrenamiento previo, la primera vez que se topa uno con una
expresin como la 5.1 anterior, puede quedarse bastante perplejo. Aunque en realidad solo
se trate de uno ms de los "idioms" (
4.13) de fondo de armario de cualquier programador
C++

6 Conversin de punteros
Un puntero de un tipo (tipoX) puede ser convertido a otro (tipoY) usando el mecanismo de
conversin o modelado de tipos, que utiliza el operador (tipoY*).

Ejemplo:

char *str;
int *ip;
str = (char *)ip;

// str puntero a char


// ip puntero a int
// asignacin str = ip (ip puntero a char)

De forma general, el operador-moldeador (tipoX *) puede convertir el operando (un puntero de


cualquier tipo) a un puntero-a-tipoX.
Como ejemplo de lo insidioso que pueden llegar a ser algunos rincones de los compiladores C++,
considere el siguiente ejemplo:
long strRchr (const String& str, char needle) {
unsigned long stLen = str.len();
for (register unsigned long i = stLen - 1; i>=0; --i) {
if (*(str.cptr + i) == needle) return i;
}
return -1L;
}
Se trata de una funcin que proporciona la posicin de la ltima ocurrencia de un carcter
(needle) en un una cadena de caracteres (str). En nuestro caso los objetos de la
clase String albergan cadenas alfanumricas. En concreto, el mtodo len() proporciona la
longitud de la cadena, y el miembro cptr es un puntero al primer carcter. La funcin devuelve -1
si el carcter no se encuentre en la cadena. En caso contrario devuelve la posicin, empezando a
contar desde cero para el primer carcter.
Aunque compila sin dificultad, la rutina anterior, produce un extrao resultado negativo en las
pruebas realizadas con el compilador BC++ 5.5, mientras que con GNU G++ 3.4.2-20040916-1
para Windows, produce un error fatal de runtime. Despus de perder un par de horas intentando
diagnosticar el problema, y haber sopesado todas las posibilidades (incluyendo que el compilador
estuviese "poseido" :-), result que estaba en el resultado de str.cptr + i (suma del puntero
con el incremento). Cuando i es un unsigned long, la suma con el puntero produce un resultado
negativo!!. Ningn otro tipo para i, produca error. Por ejemplo int o long.
Observe que este comportamiento anmalo se produce a pesar de lo sealado al respecto por el
Estndar [4], y de que los unsigned long son tipos enteros.

4.2.1 Puntero a objeto


1 Sinopsis:
Un "puntero-a-tipoX" (puntero a un objeto de tipoX), almacena la direccin de un objeto del tipo
sealado; es decir: seala a un objeto tipoX. Puesto que los punteros son objetos, puede haber un

puntero apuntando a un puntero (as sucesivamente). Los objetos a los que se apunta suelen ser
matrices, estructuras, uniones y clases.
Nota: aqu consideramos que un "objeto" es una regin especfica de memoria que puede
contener un valor (o conjunto de valores) fijo o variable. Esta acepcin del vocablo es
diferente (ms amplia) que cuando designa la instancia de una clase.

Las explicaciones que siguen son vlidas para punteros objetos de cualquier tipo (que no sean
funciones), pero cuando se trata especficamente de punteros a clases e instancias de clases,
existen ciertas particularidades en la notacin, as que les hemos dedicado un epgrafe especfico
con algunos comentarios (
4.2.1f).
Nota: tradicionalmente se considera que punteros y matrices estn estrechamente
relacionados [1], hasta el punto de ser estudiados simultneamente. De hecho, cualquier
operacin que puede efectuarse entre subndices de matrices puede efectuarse con punteros,
en general de forma ms rpida (aunque algo ms difcil de entender por el principiante).
Adems, el operador de elemento de matriz [ ] se define en trminos de ser un puntero (
4.9.16).

Los punteros a objeto tiene su propia aritmtica (rudimentaria), de forma que pueden ser
incrementados, disminuidos y comparados cuando se exploran matrices u otras estructuras
complejas.

2 Puntero nulo
Un puntero nulo es un puntero que apunta a una direccin que no corresponde a ningn objeto
del programa. Asignando la constante entera 0 a un puntero se le asigna el valor nulo. Para mejor
ligibilidad puede utilizarse la constante manifiesta NULL(definida en la librera de cabeceras
estndar <stdio.h>). Ejemplo:
int* punt1;
punt1 = 0;
punt1 = NULL;

// L.1: Declara punt1


// L.2: define punt1 como puntero nulo
// Equivalente a la anterior

Nota: la cuestin de la inicializacin del puntero nulo es uno de los puntos de controversia para los
tericos del lenguaje. En rigor, la sentencia L.2 anterior es errnea, ya que el tipo de punt1 es
puntero-a-int, mientras que 0 es una constante numrica (int por defecto); a pesar de lo cual es
aceptada sin rechistar por el compilador [2]. Lo sintcticamente correcto sera realizar un modelado
adecuado (
4.9.9):
punt1 = static_cast<int*> (0);

Es interesante observar que los punteros nulos de tipos distintos son distintos entre si. Por ejemplo,
un puntero nulo-a-int es de tipo distinto de un puntero nulo-a-char (aunque ambos coincidan en
que su Rvalue es 0), por tanto no pueden ser comparados. La comparacin de dos punteros nulos
del mismo tipo conduce a que son iguales. Ejemplo:

int* pint = NULL;


char* pchar = NULL;
if (pint == pchar) cout << "Iguales";

// Error!!

La ltima sentencia produce un error de compilacin: Error:


comparison in...

Nonportable pointer

Es un error deferenciar (
4.9.11) un puntero nulo, adems, el intento de utilizar el valor obtenido
puede dar lugar a un error de ejecucin. Por ejemplo, refirindonos al caso anterior:
int x = *pint

// Error:

Recordar que cuando falla el operador de modelado dynamic_cast (


4.9.9c) sobre un puntero,
el resultado es un puntero nulo. Tambin que algunas funciones de la Librera Estndar devuelven
punteros; incluso el operador new devuelve un puntero, y en algunas implementaciones antiguas,
el aviso de error en la operacin de este operador se realizaba devolviendo un puntero nulo.

3 Inicializar punteros
En general los punteros pueden ser inicializados como otra variable cualquiera; desde luego solo
tiene sentido que el valor sea la direccin de inicio del almacenamiento de un objeto del tipo
adecuado. En otras palabras: un valor que sea una direccin de memoria. Como existe un
operador que precisamente proporciona la "direccin" de una variable (operador de
referencia &
4.9.11), la forma usual de inicializar un puntero suele ser asignndole el valor
devuelto por dicho operador. Ejemplo:
int x = 100;
int* ptr;
ptr = &x;

// declaracin de ptr como puntero-a-int


// se le asigna la "direccin" de una variable

Tambin es muy frecuente iniciarlos con el valor devuelto por el operador new (
precisamente devuelve un puntero a objeto. Ejemplo:

4.9.20) que

class C { .... };
C* cptr;
cptr = new C(...)

// definicin de una clase C


// declara cptr como puntero-a-C
// new crea una instancia de la clase y devuelve un
// puntero al objeto, cuyo valor que es asignado a cptr

Nota: los enteros y los punteros no son intercambiables, con excepcin del cero. El cero es un
valor que nunca puede adoptar un puntero en condiciones normales, por esta razn se utiliza
para significar "no asignado" u otra circunstancia especial. Recordar que la aritmtica de
punteros permite comparar dos punteros, para igualdad o desigualdad, conNULL.

4 Punteros como argumentos


En muchas ocasiones los punteros se utilizan como argumentos a funciones. De hecho, la mayora
de las veces en que las matrices o las cadenas de caracteres pasan como argumentos, lo que en
realidad pasa es un puntero al primer elemento. Por ejemplo:

char* str = "La Tacita de Plata";


printf("La ciudad es: \"%s\"\n", str);
En este caso, la funcin printf recibe como segundo argumento str, que es un puntero a la
posicin de memoria donde comienza el almacenamiento de la cadena "La Tacita de
Plata\0".

En cualquier caso, es necesario declarar esta circunstancia en el prototipo y en la definicin de


la funcin para que el compilador conozca que el argumento pasado es una variable tipo puntero.
Ejemplo:
# include <iostream.h>
void func(int *);

// prototipo: argumento definido como puntero-a-int

void main () {
// =============
int x = 35;
int * ptr = &x;
func (ptr);
// se pasa puntero-a-int como argumento
cout << "El valor es ahora: " << x << endl;
}
void func (int * p) { // el argumento se define como puntero-a-int
*p += 10;
// el argumento es tratado como puntero
}
Salida:
El valor es ahora: 45

También podría gustarte