Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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.
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 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).
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)
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
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
pt1 + n
puntero
pt1 - n
puntero
pt1 - pt2
entero
pt1 ==
NULL
booleano
pt1 !=
NULL
booleano
pt1 <R>
pt2
booleano
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;
3. *ip +=
1;
4. ++*ip;
5. ++ip;
6. ip++;
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;
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:
*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
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;
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;
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:
// Error!!
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:
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;
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(...)
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.
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