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 Punteros

"Los punteros, junto con los goto, son una forma estupenda de crear programas imposibles de entender"  Brian W. Kernighan y Dennis M. Ritchie. "The C Programming Language" Prentice Hall 2ª Ed. 1988 p. 93.

§1 Presentación

Al hablar de punteros es inevitablemente hablar de la memoria, así que permitidme una pequeña introducción al tema.

A efectos del programador, la memoria RAM puede suponerse como una sucesión de contenedores capaces de albergar datos (podríamos imaginarlos como una sucesión de vagones de tren). Estos contenedores tienen dos atributos: dirección y contenido.

  • Dirección: un identificativo que sirve para distinguirlos. Para esto es suficiente un número entero positivo progresivamente creciente desde la posición más baja, la dirección 0 (que correspondería al primer vagón), a la posición más alta XXXXX (que correspondería al último). Es tradición informática que estos números se representen en hexadecimal ( 2.2.4b), de forma que las direcciones se suelen representar como xxxxxh en los textos de programación.

    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 más bajo hasta el máximo soportado por el hardware (máximo que puede direccionar). En la práctica la RAM instalada en un equipo es siempre inferior a este valor máximo permitido por su arquitectura [3], pero el mecanismo de memoria virtual ( H5.1) puede simular la existencia de RAM adicional mientras exista espacio en disco.

  • Contenido: el contenido correspondiente a cada dirección está siempre en binario (la memoria física solo puede contener este tipo de variables 0.1), y la capacidad de cada vagón depende de la plataforma. En la arquitectura PC, el espacio señalado por cada dirección 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 número suficiente. Por ejemplo, si ordenamos al compilador que traiga un dato que está en la dirección 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 "dirección de un objeto" nos referimos siempre a la dirección donde comienza su almacenamiento. Por contra, la expresión "dirección de memoria" se refiere a un vagón específico.

Como veremos a continuación, los punteros son un tipo de dato que sirven para almacenar direcciones de memoria, y su importancia radica en que la programación de máquinas de Von Newmann tal como las conocemos hoy ( 2), gira en torno al concepto de dirección de memoria.

§2 Sinopsis

"The most important new concept in computer science that was not already in mathematics is the concept of address". A Stepanov. En la presentación del libro: "STL tutorial and Reference Guide". D.R. Musser y otros. Addison Wesley Septiembre 2001.


Los punteros son un tipo especial de datos C++ cuyo fin específico es almacenar direcciones de objetos. Comparten las características de las variables. Es decir: tienen un valor (tienen Rvalue); pueden ser asignados (tienen Lvalue); tienen un álgebra específica (se pueden realizar con ellos determinadas operaciones); pueden ser almacenados en matrices; pasados como parámetros a funciones y devueltos por estas. Comprenden dos categorías principales: punteros-a-objeto y punteros-a-función según el tipo de objeto señalado. Ambas categorías comparten ciertas operaciones, pero tienen uso, propiedades y reglas de manipulación distintas.

Nota: más que un "tipo" deberíamos decir mejor una "familia de tipos". Tendremos ocasión de ver que existen tantos sub-tipos de puntero como tipos de objetos distintos pueden ser señalados por ellos.  También veremos que cuando se declara un puntero, se especifica a que "tipo" de objeto puede señalar; 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 "señala" a un objeto determinado cuando su valor (del puntero) es la dirección del objeto (dirección de memoria donde comienza su almacenamiento). El objeto señalado por el puntero se denomina referente. Por esta razón también se dice que el puntero "referencia" al objeto. El operador que permite obtener la dirección de un objeto (para asignarlo a un puntero) se denomina operador de "referencia" ( 4.9.11), y la operación de obtener el referente a partir del puntero se denomina "deferenciar" ( 4.9.11).

Como veremos a lo largo de este capítulo, los punteros representan magnitudes escalares (números) con casi todas las propiedades de los enteros sin signo, pero tienen sus propias reglas y restricciones para asignación, conversión, y aritmética (disponen de ciertos operadores aritméticos). Por supuesto, su tamaño es el suficiente para almacenar una dirección de memoria en la arquitectura de la computadora utilizada.

Nota: en la mayoría 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 tamaño del puntero es mayor que el tamaño de un entero.

En cualquier caso, el tamaño de un puntero Ptr puede comprobarse mediante el operador sizeof ( 4.9.13) y una sentencia del tipo:

cout << sizeof Ptr << endl;

Considere el siguiente ejemplo:

char * ptc = "A";
int entero = 100;
int * pti = &entero;
printf("Tamaño de puntero a carácter:%4d bytes\n", sizeof(ptc) );
printf("Tamaño de puntero a entero:%4d bytes\n", sizeof(pti) );


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

Tamaño de puntero a carácter:  4 bytes
Tamaño 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 utilización. Habida cuenta que esta memoria es manejada por el procesador y su circuitería 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 programación [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 dirección (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 podría considerarse de muy bajo nivel, ya que permiten manipular directamente contenidos de memoria. Por esta razón, su utilización puede presentar algunos problemas y exigen que se preste una especial atención a aquellas secciones del código que los utilizan. Recuerde que los errores más insidiosos y difíciles de depurar que se presentan en los programas C++, están relacionados con el uso descuidado de punteros. Precisamente por razón del comentario de K&R apuntado al principio y porque son frecuentemente origen de errores, algunos lenguajes prefieren evitarlos por completo, al menos su utilización explícita, y en otros casos su utilización 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 capítulo extraordinariamente importante.


Como epílogo de lo anterior y aclaración 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 más amable), digamos que la utilización de punteros exige un cierto entrenamiento mental, ya que en principio son difíciles 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 señalado 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 práctica para que estos conceptos fluyan rápidamente y sin problema.

 

  Inicio.


[1] Nos referimos a lenguajes de bajo nivel o que, como C/C++, están íntimamente relacionados con el hardware subyacente.

[2] Como botón de muestra de lo que opinan otros lenguajes sobre este particular, incluimos un comentario respecto a las limitaciones que Perl impone a la utilización de punteros (que en este lenguaje se denominan referencias): "Perl is pretty loosy goosey about what it will let you do, but not even perl is so crazy as to give people complete access to the system memory". Greg London "Impatient Perl" Abril 2004.

[3] Por ejemplo, un PC con procesador Pentium II© puede soportar hasta 4 GB de memoria, aunque es conveniente hace una puntualización: se dice que esta es una arquitectura de 32 bits porque los registros de este procesador son de 32 bits, aunque su bus de direcciones es de 64. Es decir, puede direccionar hasta 18.4 EB ( 264 ), que sería el máximo teórico sin utilizar ningún artificio adicional (como la disposición en "bancos"). Sin embargo, los compiladores para estas plataformas se construyen pensando principalmente en los registros. De forma que las direcciones de memoria se limitan a valores de 32 bits, de lo que resulta una capacidad de direccionamiento de 4 GB ( 232 ).

[4] Algún autor ha llegado a afirmar que existe un tipo de personas que son definitivamente incapaces de utilizar e interpretar correctamente los punteros. "If I may be so brash, it has been my humble experience that there are two things traditionally taught in universities as a part of a computer science curriculum which many people just never really fully comprehend: pointers and recursion". Joel Spolsky; "The Perils of JavaSchools". Más adelante, en el mismo artículo añade: "Are pointers and recursion the Latin and Greek of Computer Science?".