Você está na página 1de 42

Que es una variable?

Una computadora opera manipulando direcciones de memoria y los valores almacenados en dichas direcciones. Un lenguaje de programacion es una herramienta que permite al programador codificar operaciones binarias en un lenguaje mas cercano a nuestras lenguas naturales. Un programa que realiza la traduccion de instrucciones desde un lenguaje de programacion dado al lenguaje de maquina es un compilador. Una variable es un recurso, entre otros, para manipular un dato binario de modo mas legible. Una variable es un identificador, al igual que el nombre de una funcion, este NOMB ! representa para la maquina una localidad de memoria donde el programa puede almacenar y manipular un dato. Una declaracion de variable como"
int var;

produce una asociacion entre el nombre #var# y un espacio de almacenamiento en memoria. $or lo tanto hay dos elementos relacionados con el nombre #var#" un valor que se puede almacenar alli y una direccion de memoria para la variable, algunos autores se refieren a estos dos aspectos como el %rvalue% y %lvalue% de la variable. &demas del identificador %var%, tenemos la palabra %int% que nos indica el '($O )type* de la variable. !l tipo nos indica" +,-U&N'&. -!/0&. 0! M!MO (& )bytes* se asocian a ese nombre de variable. 1,0! 2U! MO0O .! &N (N'! $ !'&0O. los datos que se encuentren en tal localidad de memoria, +,Un byte es la menor unidad de informacion que pueden direccionar la mayoria de las computadoras. !n la mayoria de las arquitecturas el tipo char ocupa un solo byte, por lo tanto es la unidad minima. Un bool admite solo dos valores diferentes, pero es almacenado como un byte. !l tipo integer ocupa generalmente 1 bytes, un long 3, double 4, y asi con el resto de los tipos. 1,!l otro punto es la relacion entre /O 2U! 5&6 en una celda de memoria y -OMO !. (N'! $ !'&0O. /o que hay en un celda cuya e7tension es un byte es simplemente un conjunto de ocho estados posibles )4 bits* que a nivel hard8are admiten dos estados diferenciales, estados que pueden ser interpretados como #verdadero9falso#, :9+, o cualquier otro par de valores. Una celda de memoria del sector de datos, podria contener algo como lo siguiente"

2ue es esto; 0epende en gran parte del '($O )type* que hayamos asociado a esa celda )y suponiendo que e7ista tal asociacion*. !se valor interpretado como un he7adecimal es :7<+, en decimal es =>, y si fue asociada al tipo char representara la letra #a#, cuyo &scii es igual a =>. !n ninguna localidad de memoria hay algo como la letra #a#, lo que encontramos son valores binarios que en caso de estar asociados a char y en caso de que lo saquemos en pantalla como char hara que veamos encendidos ciertos pi7eles de pantalla, en los cuales reconoceremos una representacion de la letra #a#.

/a representacion binaria de datos ocupa demasiado espacio, por ese motivo es preferible utilizar el sistema he7adecimal, ademas de ser muy facil de traducir a binario es mas economico que este o el decimal. Observar los bytes de un sector de memoria de un programa facilita la comprension sobre el modo en que cada tipo )type* se asocia a direcciones de memoria. .upongamos un programa que declara, define e inicializa las siguientes variables"
int main() { int a = 5; long b = 8; char cad[ ]= "abcd"; char ch = '6'; char hh = 6; etc....

/a representacion de estos datos en memoria, en el segmento de datos, tal como lo muestra un debugger, tendra el siguiente aspecto )se omiten caracteres problematicos para navegadores y dejamos constancia que diferentes compiladores pueden ordenar los datos de otro modo*"

ffd: ........................ ffe: ............<abcd.. fff: .......................

1: 1: 1: 1: :: 4? +1 :: :: :: ?< ?? B- :3 :: ?? ?< ?< :: :: ?< ?? -> :3 :< @< <+ <1 <@ <3 :: :: :4 :: :: :: :A :: :: :: :B :+ :: :: :: :: :: ::

/os datos que se han declarado primero en el codigo )int a* figuran al final, los bytes :A :: son la representacion de la variable entera #a# de valor B A, los cuatro bytes :4 :: :: :: lo son del long #b#, luego sigue el array %aaaa%, el char #<# que corresponde con el he7adecimal :7@<, y por ultimo un char seteado con el valor entero <. &demas podemos realizar las siguientes observaciones" +, 2ue el segmento de datos almacena los datos comenzando desde el final ):7ffff*. /a primera variable declarada y definida es el entero #a#, que no esta verdaderamente en el final del segmento, es asi porque esos valores )como :B :+* guardan valores de .'&-C para restablecer algunos registros cuando el programa salga de main)* y termine. .obreescribir ese valor podria producir un crash. 1, 2ue la variable entera de valor A guarda este valor ubicando los bytes al reves. /o logico seria que la representacion fuera :: :A, pero los bytes estan invertidos, esto es una norma general de la mayoria de los procesadores y responde a una pauta de mayor eficiencia en la lectura de variables numericas. @, !l array #cad# se declara de modo implicto con A bytes, las cuatro letras mas el caracter terminador #D:#, se ocupa un byte mas porque un numero par de bytes es mas eficiente. Observese que un array no invierte la posicion de sus elementos. 3, Un char ocupa e7actamente un byte. !l primer char esta definido con el caracter #<# que corresponde al ascii :7@<, la segunda variable char )hh* es seteada a partir de un valor entero )<* lo que genera una conversion implicita de tipos. .e podria profundizar mas el tema de que funcion tiene este sector de memoria, su relacion con la pila ).'&-C* y los modelos de memoria, pero eso se vera en otros apartados. $or ahora es importante tener en cuenta la relacion entre el tipo )type* usado para declarar una variable y el modo en que se almacena en memoria. !n la siguiente tabla se encuentran mas ejemplos"

DECLARACION Inicializacion int aE char chE char cadFGB%hola%E long aE long aE long aE aB3 aB:7+1@3 a B <AA@A a B AE ch B #e#E ,

Representacion Numero en memoria de bytes :A :: <A <4 <? <- <+ :: :3 :: :: :: @3 +1 :: :: ff ff :: :: 1 + A 3 3 3

-uando en el flujo de un programa se asigna un valor a una variable lo que sucede es que la localidad )o localidades* de memoria asociadas a la variables son seteadas con tal valor. /a asociacion entre localidades de memoria y variable no siempre e7iste desde el comienzo al final de un programa. /as variables declaradas como #locales# a una funcion solo tienen asociada una localidad de memoria mientras el flujo del programa se encuentra en tal funcion, al salir de la misma tales localidades seran usadas por otros datos. !n cambio las variables #globales# o declaradas como #static# conservan su localidad de memoria durante toda la ejecucion del programa.

Que es un array?
Un array es una coleccion ordenada de elementos del mismo tipo )type*, estos tipos pueden ser los que proporciona el lenguaje, como char, integer, float, long integer, etc., o bien puede tratarse de un tipo definido por el programador, como una estructura o una clase. !stos elementos se encuentran ordenados en celdas consecutivas de memoria. Heamos los siguientes ejemplos" Declaracion e inicializacion int aF3GBI1JE char a FG B I%Mensaje +%JE char a F4G B IholaJE long a FG B I=, +<, :71@b1aJE Representacion en memoria Bytes 173B4 173B4

int a FGB I@, @3A, A3, 3JE :@ :: <@ :+ >1 :+ :@ 1> :1 :: :: :: :: :: :: ::

3d <A <e >@ <+ <a <A 1: @+ :: =K+B +: <4 <? <- <+ :: :: :: :: >K+ B 4

:= :: :: :: +1 :: :: :: 1a @b @ 7 3 B :1 :: +1

!l tipo )type* del array determina cuantos bytes ocupa cada uno de sus elementos, y tambien de que modo se almacena el dato. !s importante mencionar que este modo de inicializacion es solo posible cuando se realiza en la misma linea que en la declaracion, no es posible inicializar al mismo tiempo varios elementos de un array si lo hacemos en una linea diferente a la de la declaracion. 'ambien hay que mencionar el hecho de que si damos mas elementos inicializadores que los que figuran entre corchetes se

genera un error de compilacion, si damos menos elementos el compilador setea el resto de los elementos con el valor #:#. Una cadena en -9-KK es representada internamente como un array de tipo char y utiliza un caracter #terminador# para indicar el fin de la cadena, ese caracter es el correspodiente al &scii B :. $ara mas detalles ver cadenas estilo -9-KK . /a notacion %NombreLarrayFint nG% nos permite seleccionar cada uno de los elementos de ese array, esa e7presion tiene el mismo tipo )type* que un elemento individual, y esto puede ser importante para distinguir mas claramente las asignaciones y conversiones posibles.

Que es un puntero?
Un puntero es un tipo especial de variable, que almacena el valor de una direccion de memoria, esta direccion puede ser la de una variable individual, pero mas frecuentemente sera la de un elemento de un array, una estructura u objeto de una clase. /os punteros, al igual que una variable comun, pertenecen a un tipo )type*, se dice que un puntero #apunta a# ese tipo al que pertenece. !jemplos"
int* pint; char* pchar; $echa* p$echa; !eclara "n p"ntero a entero #"ntero a char #"ntero a ob%eto de cla&e '$echa'

(ndependientemente del tamaMo )sizeof* del objeto apuntado, el valor almacenado por el puntero sera el de una unica direccion de memoria. !n sentido estricto un puntero no puede almacenar la direccion de memoria de #un array# )completo*, sino la de un elemento de un array, y por este motivo no e7isten diferencias sintacticas entre punteros a elementos individuales y punteros a arrays. /a declaracion de un puntero a char y otro a array de char es igual. &l definir variables o arrays hemos visto que el tipo )type* modifica la cantidad de bytes que se usaran para almacenar tales elementos, asi un elemento de tipo #char# utiliza + byte, y un entero 1 o 3. No ocurre lo mismo con los punteros, el tipo no influye en la cantidad de bytes asociados al puntero, pues todas las direcciones de memoria se pueden e7presar con solo 1 bytes )o 3 si es una direccion de otro segmento* Heamos los efectos de un codigo como el siguiente, en la zona de almancenamiento de datos"
char cad[] = "hola"; char * p; p = cad; #"ntero 'p' ap"nta a 'cad'

!l puntero esta en la direccion :7ffee pero el valor que hay en esa localidad de memoria es otra direccion, los bytes %?: ??% indican que el puntero apunta a ???:, donde comienza la cadena de caracteres #cad# con el contenido #hola# mas el cero de fin de cadena.

!n las lineas de codigo no hemos indicado a que caracter del array apunta el puntero, pero esa notacion es equivalente a"
p = 'cad[(];

que indica de modo mas e7plicito que se trata de la direccion del primer elemento de ese array de caracteres. !l juego con las direcciones puede ilustrarse tambien del siguiente modo"
$$ee $$e$ $$$( )( )) 6* 6* 6* 6* (

N,,,,, !l puntero ocupa dos bytes para representar la direccion ???:, direccion N,,,,, N,,,,,, cadF:G. .$rimer char del array de caracteres, direccion apuntada por el N,,,,,, cadF+G N,,,,,, cadF1G N,,,,,, cadF@G N,,,,,, cadF3G ?in del array, caracter ascii B : de fin de cadena

a la que #apunta#.

puntero
$$$* $$$+ $$$, f$$-

$uesto que un puntero tiene como valor una direccion de memoria, es logico que al llamar a funciones de impresion con un puntero como argumento, la salida en pantalla sea la de una direccion de memoria. $ara este tipo de pruebas es interesante usar la libreria iostream.h de -KK, pues no obliga a especificar el formato )como hace printf *. $ara un puntero #p# la salida en pantalla sera algo similar a lo siguiente"
co"t..p; print$("1p"2p) &ale/ (08$8+$$$(; &ale/ )))(

!n este caso se trata de un puntero que almacena en 1 bytes una direccion de memoria, la cual es ???:. $orque razon la impresion con #cout# nos da 3 bytes; $orque agrega 1 bytes )4f y 41* para indicar el #segmento# donde se encuentra esa direccion. .e trata en todo caso de una misma localidad de memoria, con distinto formato de presentacion en pantalla. /a salida en pantalla de un puntero a char es diferente, pues es tratado como apuntando a una cadena de caracteres, en tal caso no sale en pantalla una direccion de memoria, sino un conjunto de caracteres hasta encontrar el #D:#. Un puntero puede almacenar la direccion de )%apuntar a%* muy diferentes entidades" una variable, un objeto, una funcion, un miembro de clase, otro puntero, o un array de cada uno de estos tipos de elementos, tambien puede contener un valor que indique que no apunta actualmente a ningun objeto )puntero nulo*.

ipos de!inidos por el pro"ramador


'ipos como #bool#, #int# o #char#, son %tipos predefinidos%, pertenecientes al lenguaje. !n -KK al igual que otros lenguajes, es posible definir tipos nuevos. /as enumeraciones, uniones, estructuras y clases, son tipos nuevos que implementa el programador.

/a declaracion de un tipo no produce ningun efecto en memoria, no hay ningun identificador donde almacenar un dato, por esa razon no tendria sentido, dentro de la definicion de una estructura o clase , intentar dar un valor a sus datos, seria lo mismo que intentar dar un valor a un tipo predefinido, por ejemplo"
long = 8;

$ara asignar un valor necesitamos un objeto, pues un objeto implica una region de memoria donde almacenar un valor. !l almacenamiento en memoria de una union, enumeracion o estructura )-*, no presenta importantes cambios respecto a los tipos predefinidos, sus elementos se ordenaran de modo consecutivo de acuerdo a su #sizeof#. especto a -, -KK aporta un nuevo tipo predefinido, las clases, entidad que no solo es un agregado de datos sino tambien de funciones, y que por ello presenta novedades de importancia respecto a los tipos anteriores.

Clases
Una clase es basicamente un agregado de datos y funciones para manipular esos datos. /as clases, y la programacion #orientada a objetos# en general, ha representado un gran avance para produccion de soft8are a gran escala, los recursos de herencia, encapsulamiento, ocultacion de datos, clases virtuales, etc., estan pensados con esa finalidad. &qui solo nos detendremos en la nocion minima de #clase# y el modo en que es almacenado un objeto en memoria. .upongamos una clase muy simple"
cla&& gente { char nombre[*(]; int edad; p"blic/ gente (char*cad2 int a) { &trcp3(nombre2cad); edad = a; 4 4;

.e trata de una clase cuyos miembros son dos datos y una sola funcion. Una vez declarada la clase podemos definir objetos como pertenecientes a ese tipo. Una clase no ocupa espacio, pero si un objeto perteneciente a esa clase. !l espacio ocupado en memoria por tal objeto puede ser conocido a traves de #sizeof#.
gente pp*; co"t..&i5eo$(pp*); &aca en pantalla '*+'

!l valor podria ser ligeramente diferente segun el compilador, por efecto de optimizacion. /o importante es observar que el monto de memoria del objeto )retornado por sizeof*, esta determinado por la suma del espacio ocupado por los datos, #sizeof# no tiene en cuenta a la funcion. -ada objeto de tipo #gente# ocupara +1 bytes, pues posee una copia individual de los datos de clase, en cambio hay una sola copia del miembro funcion )aqui el constructor* utilizado por todos los objetos. 0eclaremos dos objetos de tipo #gente#"

gente pp*("gerardo"2 ,,); gente pp+("mig"el"2,-);

Observaremos ahora que efectos producen estas entidades #pp+# y #pp1#, en memoria. /os datos que utilizaremos se obtienen en 'urbo-KK )cualquier version* posando el cursor sobre el objeto que nos interesa )aqui #pp+# y #pp1#* y pulsando #&ltKf3#, tambien consultaremos los registros de la -$U )con %Oindo8s9 egisters%*. !n un programa, que define la clase #gente# y dos objetos )pp+ y pp1* inicializados como muestran las lineas de codigo previas, se puede observar lo siguiente"

!l valor especifico de cada dato )como el valor de segmento* puede variar con cada ejecucion, lo que cuenta es la relacion entre tales valores. (nterpretemos estos datos. +, !n la ventana de cada objeto )pp+ y pp1* figura en primer lugar la direccion de memoria donde almacena sus valores, ambas direcciones tienen el mismo valor de segmento ):74?4<*, que coincide por otra parte con el valor de D# $se"mento de datos% y de ## $se"mento de stac&% de la -$U. .us direcciones difieren ligeramente en offset, la resta de los mismos ):7??!& , :7??0!* es igual a +1, que es el espacio que ocupa )en bytes* cada objeto en memoria. 1, !sos +1 bytes por objeto corresponden a +: para la cadena de caracteres )#cad#* y 1 para almacenar el entero )#edad#*. !stos datos estan almacenados alli donde indica el offset, no en otro sitio, por lo tanto un puntero al objeto #pp+# apuntara )en este caso* a la misma direccion de memoria que un puntero a su elemento #cad#, y otro tanto para #pp1#. /as datos miembros se e7ponen con sus nombres a la izquierda y el valor que contienen a la derecha. /a cadena de caracteres es terminada en #D:# )seguido de caracteres aleatorios*, y el entero es mostrado en formato decimal y he7adecimal @,0ebajo, y separado por una linea, se encuentra un espacio donde se enumeran las funciones miembro de la clase. &lli encontramos el prototipo de la funcion miembro y al lado la direccion de memoria donde se inicia su codigo. !se es el valor que almacenaria un puntero a dicha funcion. Observese que tal direccion es la misma para ambos objetos, por la razon antes mencionada de que hay solo una copia de !unciones miembro por ob'eto( !l segmento donde se encuentra tal funcion se corresponde con el valor que muestra la ventana -$U para C# $se"mento de codi"o%( $odemos sintetizar lo visto respecto a clases del siguiente modo" ,Una clase no es un #dato# )es un tipo*, no tiene una localidad de memoria asociada y por lo tanto no puede almacenar ningun valor. ,Un ob'eto de tal clase si define una region de memoria, un espacio de almacenamiento de datos. !sta es la diferencia entre #clase# y #objeto#. ,-ada objeto de una misma clase posee una copia propia de cada uno de los datos miembros de la clase, pero comparte una misma copia de las funciones miembros.

$or otra parte, un array de objetos )instancias de clase* es almacenado como una sucesion consecutiva, mientras que un puntero a objeto sera )como todo puntero* un par de bytes que apunte a una direccion de memoria donde se almacena el objeto

)unteros I
Declaracion e inicializacion
Un puntero, como cualquier variable u objeto, ademas de ser declarado )para comenzar a e7istir* necesita ser inicializado )darle un valor de modo controlado*, lo cual se realiza mediante el operador de asignacion )#B#*. 0esde que el puntero es declarado almacena un valor, el problema es que se trata de un valor aleatorio, intentar operar con un puntero sin haberlo inicializado es una frecuente causa de problemas.

Asi"nacion erronea* +Cannot assi"n(((+


!n primer lugar veremos un caso simple de asignacion erronea para comprender el mensaje enviado por el compilador.
void main(){ int a; int* b; b = a; 4

6rror

!l programa no compila, y recibimos el mensaje de error"


"7annnot a&&ign 'int' to 'int near*' en $"nction main();

5emos tratado de inicializar el puntero #b# asignandole un valor equivocado, de otro tipo. !l analisis de los mensajes de error siempre es instructivo, profundizemos en este. +, !n primer lugar" P2ue es un %int near,% ;. /os punteros pueden ser clasificados como #near# )cercano* o #far# )lejano* de acuerdo a si la direccion apuntada se encuentra en el mismo se"mento que el puntero. /as principales diferencias se e7ponen en el siguiente cuadro. ipo de puntero Caracteristicas Cantidad de bytes -ue utiliza el puntero

near !ar

/a direccion apuntada se encuentra dos , )offset* en el mismo segmento que el puntero .e encuentran en diferente segmento cuatro , )segmento""offset*

.i un programa no requiere de una gran cantidad de datos significa que pueden entrar en un solo segmento, y los punteros seran #near# por defecto, en caso contrario el default sera #far#. !sto es determinado directamente por el modelo de memoria utilizado por nuestro programa. 1,!n segundo lugar, el mensaje nos indica una discordancia de tipos. Uno es #int#, y el otro es #int near,#, si obviamos la caracteristica de #near# vemos que la e7presion %intQ% coincide con nuestra

declaracion del puntero. /o mas instructivo de esto es comprender que el asterisco pertenece al tipo )type*, no al nombre )#b#*. &lgunos autores discuten sobre cual de las dos siguientes declaraciones es la mas adecuada para declarar un puntero"
int *b; int* b;

&mbas son perfectamente validas, la unica diferencia es que el primer caso se sugiere que #Q# forma parte de #b#, y en el segundo que #Q# forma parte del tipo. /o recomendable es adoptar la segunda forma, la primera se presta a confundir el operador #Q# con el operador de #indirection#, y es muy importante comprender que aqui no hay nada de #indirection#, es solo una declaracion de un identificador )b*, ligado a un tipo )intQ*. !s el mensaje del compilador el que nos indica esta ultima interpretacion. $ara que el programa compile sin problemas es necesario utilizar el operador #R# antes del nombre de la variable, el efecto del operador es devolver la direccion en memoria de la variable, la cual se asigna naturalmente a un puntero.
void main(){ int a; int* b; b = 'a; 4

6l p"ntero 'b' ap"nta a 'a'.

Una variable individual de tipo #'# y un array de elementos de tipo #'# pertenecen a tipos diferentes, no es posible la asignacion entre un entero y un array de enteros. (ntentemos sin embargo tal asignacion"
void main(){ int a; int b[-]; a = b; 4

8&ignacion erronea2 no compila

/o mas interesante del ejemplo es que el mensaje de error es similar )pero inverso* al de nuestro primer ejemplo fallido"
"7annnot a&&ign 'int near*' to 'int' en $"nction main();

/o cual puede resultar sorprendente, pues en el ejemplo no hemos declarado ningun puntero, solo un entero y un array de enteros. /o que esta sucediendo es que el compilador se esta refieriendo al array de enteros como un %int nearQ%, como un puntero. Un array y un puntero no son e7actamente lo mismo, hay algunas diferencias, pero la relacion que e7iste entre ambos es muy estrecha y la sinta7is aplicable a ambas entidades es en gran parte identica. !sta relacion e7plica que el siguiente ejemplo compile bien sin ninguna complicacion"
void main(){ int a [-]; int* b; b = a; 4

o bien 99: b = 'a[(];

$odria haberse esperado algun problema, puesto que no hemos obtenido la direccion del array con el operador #R#, pero no ocurre asi, el solo nombre del array es tomado como sinonimo de la direccion de su primer elemento )o puntero a su primer elemento*. Bien, esta ha sido una introduccion para comprender el mensaje de error tipico en una inicializacion fallida. !l intento de asignar una variable individual a un array produce un mensaje de error distinto )%/value requerido%*, ver &ne7o1. Heamos ahora ejemplos de inicializaciones de punteros correctas.

Opciones de inicializacion
Un puntero puede ser inicializado con la direccion de memoria de un objeto, tal objeto debe pertenecer a un tipo acorde al tipo al que apunta el puntero. $uede tratarse de la direccion de un elemento de un array o de una variable individual, el operador #R# antepuesto a un objeto nos devuelve su direccion de memoria. 'ambien puede utilizarse un %literal%, ya sea numerico, de caracter, o de otro tipo, y puede inicializarse como puntero nulo, en este caso esta permitido usar el :, el unico entero permitido, por su equivalencia con el valor NU//. .uponiendo un tipo cualquiera %'%, son inicializaciones validas las siguientes" )untero inicializado a Declaracion e inicializacion Declaracion e inicializacion partir de* en una misma linea desdobladas .n ob'eto individual ' 7E .n array de ob'etos ' 7 F+:GE Otro puntero del mismo tipo 'Q 7E /alor 0 1 puntero nulo
;"ll=(

'Q ptr B R7E 'Q ptr B R7F:GE 'Q ptr B 7E 'Q ptr B 7E 'Q ptr B :E 'Q ptr B NU//E 'Q ptr B FliteralG

'Q ptrE ptr B R7E 'Q ptrE ptr B R7F:GE 'Q ptrE ptr B 7E 'Q ptrE ptr B 7E 'Q ptrE ptr B :E 'Q ptrE ptr B NU//E 'Q ptrE ptr B FliteralGE

.n literal

.obre este cuadro caben las siguientes aclaraciones" +,(nicializar un puntero apuntando al primer elemento de un array admite dos notaciones equivalentes, en la segunda se sobreentiende que el elemento apuntado es el primer elemento del array. 1,/a equivalencia entre el valor : )cero* y NU// es de uso general, sin embargo e7isten compiladores que dan a NU// un valor diferente a cero. @,Un #literal# debe ser el apropiado para el tipo de puntero inicializado. .i es un puntero a char,

una cadena de caracteres cualquiera )ej" %hola%* sera un literal adecuado, si se trata de tipo numerico, para un int %3% sera apropiado. .i tomamos como ejemplo el tipo %char%, siguiendo al cuadro anterior, tenemos las siguientes opciones de inicializacion" )untero inicializado a partir de* .n elemento
char ch;

Declaracion e inicializacion en Declaracion e inicializacion una misma linea desdobladas


char* p = 'ch; char* p = cad; char* p = 'cad[(]; char* p = (; char* p = ;=>>; char* p; p = 'ch; char* p; p = cad; char* p; p = 'cad[(]; char* p; p = (; char* p; p = ;=>>; char* p; p = ptr; char* p; p = "ca&a";

.n array
char cad[*(];

/alor 0 1 puntero nulo

;"ll=( (( e& el "nico valor entero <"e p"ede iniciali5ar "n p"ntero) char *ptr; "ca&a";

Otro puntero $ya inicializado% char* p = ptr; .n literal de cadena


char* p = "ca&a";

.e ha insistido lo suficiente en que un puntero almacena como valor una direccion de memoria, por eso la presencia de un #literal#, en esta ultima tabla el literal de cadena %casa%, puede sorprender. !s importante tener claro que todos los literales se almacenan desde el comienzo del programa en un lugar del segmento de datos, no es posible obtener su direccion )por medios normales* pero e7iste, es al comienzo de dicho segmento, el mismo sitio que se reserva a valores constantes y variables globales. Un literal es tratado como constante, esto es lo que permite que una funcion pueda retornar una constante sin temor a que dicho almacenamiento se pierda al salir de la funcion, un literal no es una variable #local#. No hay obstaculos para inicializar un puntero con una variable constante, por lo tanto lo mismo se aplica a literales de cualquier tipo. /as tablas anteriores no abarcan todos los casos posibles de inicializacion de punteros, aun no se han mencionado los casos donde el puntero apunta a una funcion o un objeto miembro de una clase, ni la opcion de inicializar a traves de memoria dinamica.

Inicializacion a traves de memoria dinamica


!sta modalidad se diferencia de todas las enumeradas hasta ahora y puede considerarse como la principal. 'odas las formas vistas hasta aqui asignan al puntero la direccion de memoria de otra entidad )elemento, array, puntero, literal* ademas del caso especial del valor NU//. 6a se ha mencionado que la declaracion de un puntero no implica la reserva de memoria, salvo 1 bytes

para almacenar una direccion, por esa razon podria decirse que el puntero, cuando es inicializado por otro elemento, #vive# de la memoria que le aporta el objeto al que apunta. /a reserva de memoria dinamica requiere el uso obligado de un puntero, el cual apuntara al comienzo de la zona reservada. /o diferente aqui es que se trata del unico caso donde el puntero no necesita de otro elemento que le aporte memoria necesaria, no necesita apuntar a algun otro objeto. -uando reservamos dinamicamente 3: bytes para un puntero a char, operaremos con el puntero #como si# apuntara a un segundo objeto )un array de caracteres*, pero tal array no e7iste. & pesar de no e7istir propiamente un segundo objeto, sigue siendo esencial, el tipo )type* segun el cual se declara el puntero, pues esto determina el modo en que el puntero nos permitira acceder a tal zona de memoria. No hay un #objeto apuntado#, pero el puntero se conduce igual que si lo hubiera, por esa razon hablaremos en general del #objeto apuntado# por el puntero, sin aclarar el caso especial que estamos considerando. !n -KK la reserva y liberacion de memoria dinamica se realiza a traves de los operadores ne2 y delete, y su sinta7is, para un puntero de nombre #ptr# es la siguiente"
Reserva Elemento individual de tipo 'T' ?* ptr = ne@ ?; Liberacion delete ptr;

Array de 'n' elementos ?* ptr = ne@ ?[n]; delete [] ptr; de tipo 'T'

&traves del operador ne2 solicitamos una cierta cantidad de memoria dinamica, es posible que no e7ista suficiente memoria disponible, en tal caso el operador nos devolvera un puntero NU// )o apuntando a :*, y es por esta razon que luego de una solicitud es recomendable inspeccionar si el puntero devuelto es nulo. !sta seria la respuesta #clasica# a una reserva fallida de memoria dinamica, sin embargo e7isten diferentes compiladores que, ajustandose al standard -KK no devuelven un puntero nulo sino que lanzan una e7cepcion )badLalloc*. &lgunos viejos compiladores no reconocen la opcion de borrar el puntero con corchetes vacios y nos e7igen que especifiquemos el numero de bytes a borrar )los mismos que los reservados*, '-K K a partir de su version @.: admite esa notacion. !scribir %delete ptr;", sin los corchetes, solo libera el primer elemento del array, y es por lo tanto un error importante.

Desre!erenciacion $+indirection+%

Concepto
Un puntero almacena una direccion de memoria de alguna entidad, esto en si mismo no seria demasiado util si no fuera posible, a traves del puntero, acceder a lo que esta almacenado en esa direccion. .egun el creador de -KK" %>a operacion $"ndamental de "n p"ntero e& desreferenciar2 e& decir2 re$erir al ob%eto al <"e ap"nta el p"ntero." ).troustrup, +==>*. & continuacion desarrollaremos esta definicion. /a funcion de #desreferenciar# un puntero es llevada a cabo por el operador #Q#, que ademas cumple otras funciones en -KK. -omo su papel es complementario a una de las funciones del

operador #R# se comenzara estudiando la relacion y diferencia de estos dos operadores &mbos operadores tienen mas de un sentido dependiendo del conte7to en que aparecen, por lo tanto son casos de sobrecarga de operadores. Heamos sus distintos usos" O)ERADOR Q Multiplicacion int a B @, bB1,cE c B a Q bE .sos 0eclaracion type puntero int nE intQ p B nE R Operacion Bit8ise &N0 char aB:7@>E a RB:7:?E 0eclaracion del type referencia int aE int Rb B aE

0ereferencing )indirection* eferencing coutNNQpE coutNNRaE !l primer uso de cada operador se distingue claramente de los otros dos, derivan de - y no tienen relacion con el tema punteros. /os que figuran en segundo lugar pertenecen a la sinta7is basica de declaracion de punteros y referencias. Nos concentraremos en el tercer significado de estos operadores. !l papel opuesto y complementario del tercer uso de ambos operadores se podria sintetizar asi" dadas las siguientes declaraciones"
int v = -; int* p = 'v;

!l puntero #p# es equivalente a la direccion de memoria a la que apunta. coutNNp memoria saca en pantalla una direccion de

/a variable #v# es equivalente al valor que almacena coutNNv saca en pantalla #3#

)por ej" (08$A($$$() Mientras que la e7presion #Qp# es sinonimo del elemento individual que se encuentra en la localidad apuntada por el puntero coutNNQp saca en pantalla #3# Mientras que la e7presion #Rv# es un sinonimo de la direccion de memoria donde se encuentra esa variable coutNNRv memoria saca en pantalla una direccion de )ej" (08$A($$$() -omo puede observarse, el efecto de ambos operadores es inverso, en un caso dada una localidad de memoria se accede al elemento almacenado en ella )el caso de #Q#*, en el otro )#R#* dada una variable accedemos a la direccion de memoria donde almacena su valor. !l termino usado para este efecto del operador #Q# es el de #indirection# o #dereferencing# traducido generalmente como #indireccion# o #desreferenciacion#. .u sentido mas llano seria" operador -ue

permite re!erirnos al elemento individual apuntado por el puntero, en lugar de la direccion en que ese elemento se encuentra almacenado. & veces se utiliza, para ejemplificar la #indirection#, un puntero que apunta a char, estos ejemplos pueden oscurecer el sentido del termino #indirection#, en especial porque con tales punteros la linea %coutNNp% no hubiera sacado en pantalla una direccion de memoria, sino una cadena de caracteres.

El caso especi!ico de un puntero a c3ar


0adas las siguientes declaraciones e inicializaciones"
char cad[] = "hola"; char* ptr = cad; 'indirection' 8<"i el '*' e& "n indicador de tipo2 no de

!l puntero #ptr# apunta a #cad#, al char inicial de #cad#. Heamos ahora que saldria en pantalla con #p# y con #Qp#, para el caso volveremos a usar la libreria %iostream.h% de -KK, pues las funciones de impondrian su formato a la salida.
co"t..ptr; co"t..*ptr; &ale en pantalla/ &ale en pantalla/ "hola" 'h'

/o que puede desorientar aqui es que #ptr# no imprima en pantalla una direccion de memoria, que es lo esperable tratandose de un puntero. .e trata de una caracteristica propia de las !unciones que tratan con punteros a char, y no de un rasgo diferencial de los punteros a c3ar, estos tienen las mismas caracteristicas generales de cualquier puntero. Utilizando una funcion - de %stdio.h%, las lineas anteriores son equivalentes a
print$("1&"2 ptr); print$("1c"2 *ptr);

&nalicemos el funcionamiento de printf. !sta funcion, recibe como argumento un puntero a c3ar, algo cuyo tipo es c3ar,, es decir una direccion de memoria. !n - o -KK no hay otro modo de pasar un array a una funcion que a traves de una direccion de memoria. !l especificador de formato, +4s+, le indica a la funcion que interprete esa direccion como siendo el comienzo de una cadena de caracteres )la #s# es de #string#*. /o que la funcion hace es interpretar los bytes, uno a uno, como indicando caracteres ascii, y los sacara ordenadamente en pantalla hasta encontrar un #D:#, sin importar donde se encuentre ese #D:# o si e7cede o no la capacidad del array original. !n -KK, el flujo de salida #cout# y el operador de insercion #NN#, no requieren de un formato especifico para sacar algo en pantalla, esto significa que imponen un formato predeterminado segun el dato enviado como parametro. !n el caso de que este parametro sea un puntero a char imponen el formato %cadena de caracteres%, e7actamente igual que printf con formato %Ss%. !sto no es obvio, dado que se trata de un puntero podrian sacarlo en pantalla como una direccion de memoria, pero no ocurre asi. !s por esta razon que la idea de #indirection# se oscurece en relacion a #punteros a char#, pues las funciones standard de impresion en pantalla de - y -KK no tratan a tal puntero como una

direccion de memoria mas )aunque lo sea*. .iendo #p# un puntero a tipo char, para las funciones standard de impresion" 5p5 es la cadena apuntada6 5,p5 el caracter individual apuntado(

Asi"nacion de punteros
Un puntero puede ser asignado a otro puntero del mismo tipo a traves del operador #B#. !l significado de tal asignacion es similar al de una asignacion entre variables, el valor almacenado en el elemento de la derecha se copia en el elemento de la izquierda. .olo que en el caso de punteros este valor es una direccion de memoria, y esto puede producir un efecto distinto al esperado.
void $ (char* cad*2 char* cad+) { cad* = cad+; *cad* = ','; ................................. 4 char "no = "****"; char do& = "++++"; $("no2 do&);

6$ecto/ modi$icacion de cadena "do&".

>lamado a $"ncion $();

/a funcion '$()' recibe dos punteros a char desde otra funcion, sigue luego una asignacion de #cad1# en #cad+#, y una modificacion de un char a traves de desreferenciacion del puntero. .i la intencion era copiar el contenido de la cadena original %dos% en la cadena %uno%, para modificar %uno% sin alterar %dos%, estamos ante un error. !l caracter #@# se copiara en la cadena original %dos%, por la razon de que luego de la asignacion de punteros )cad+Bcad1* ambos apuntan a %dos%. 5ay casos donde puede ser util que dos punteros apunten a una misma direccion, y entonces sera correcto asignar punteros mediante el operador #B#, pero si lo que se busca es copiar el contenido de un array, entonces se debe hacer de otro modo, copiando uno a uno los elementos de dicho array. 0ados dos punteros )%pt+% y %pt1%* a array, que apuntan a direcciones diferentes )son dos arrays diferentes*, los efectos de una asignacion de punteros y copia de array son los siguientes" Operacion pt+ B pt1E !fecto Asi"nacion de punteros. /o que se copia realmente son los 1 )o 3* bytes de direccion de memoria. !l puntero #pt+# deja de apuntar a al array original, ahora apunta a la misma direccion que #pt1#.

Copia de array. /a copia se realiza elemento por elemento. .e copian tantos 8hile )Qpt1TB:* elementos como caracteres tenga el array #pt1#. !n el caso de una cadena de Qpt+Bpt1E caracteres,podemos confiar en el #D:# para saber cuantos elementos copiar. !s muy importante diferenciar ambas operaciones. Un array no puede ser copiado mediante el operador de asignacion #B#, hay que copiar elemento por elemento, un puntero puede ser copiado

con tal operador, pero el efecto provocado puede ser distinto al efecto deseado. /a confusion entre copia de punteros y copia de array puede provocar otro tipo de problemas en relacion a memoria dinamica o constructores de copia, problemas que se analizan mas adelante.

)unteros II
)unteros a 5void5
Un puntero puede apuntar a un objeto de cualquier tipo, predefinido por el lenguaje o definido por el usuario. #Hoid# es el nombre de un tipo, pero su uso esta sujeto a mayores restricciones respecto a otros tipos. !l termino #void# puede ser usado como tipo de una funcion o de un puntero, pero no para declarar un objeto. /as declaraciones posibles de tipo void, en - y -KK, se resumen en el siguiente cuadro. Declaraciones Objeto de tipo void !j"
void 0;

C77

No permitido( 8ensa'e de error * +Ob'eto de sizeo! desconocido(+ !l compilador no esta en condiciones de determinar el monto de memoria que requiere el objeto.

etorno de un funcion #i"ni!ica -ue la !uncion no retorna nin"un valor !j"


void $"nc(){

...................... $untero a void !j"


void* p;

)#tipo pseudodevuelto#*. Un puntero a void es tratado $untero a objeto de tipo desconocido. como un puntero a char. equiere conversion e9plicita a otro tipo Conversion implicita( antes de ser utilizado.

!n - se accede a bytes no vinculados a ningun tipo mediante punteros a char, esto es natural si se considera que un #char# ocupa + byte de almacenamiento, y de ahi que e7ista conversion implicita entre ambos tipos. /a funcion - standard %memset)*%, retorna un puntero a void. !l siguiente codigo es aceptable en -"
void $(char*cad2 char ch2 int n){ char* &; & = mem&et(cad2ch2n); 4

conver&ion implicita de void* a char* valido en 72 pero no en 7BB.

-KK no lo permite debido a su mas estricta comprobacion de tipos. /anza el mensaje de error, %no se puede convertir voidQ a charQ%. 5ay otras funciones similares a memset que derivan de y retornan void, algunas declaradas en %mem.h% y %string.h%. 'odas pueden ser utilizadas igualmente en -KK, pues la cadena afectada por la funcion es enviada como parametro. .i se

quisiera utilizar el puntero de retorno seria necesario un puntero a %void% o una conversion e7plicita, por ejemplo"
& = (char*)mem&et(cad2ch2n); valido en 7BB

!l significado en -KK de un puntero a 5void5 es el de un puntero -ue apunta a una zona de memoria no inicializada, memoria #en bruto#, o en la cual se encuentra almacenado un objeto de tipo desconocido, en general se trata de codigo que trabaja a nivel hard8are o relacionado con administracion de memoria. /as operaciones permitidas y no permitidas, en -KK, para un puntero a void se resumen en el siguiente cuadro" +, &signar a voidQ un puntero de cualqueir tipo Operaciones 1, &signar un void a otro void permitidas @, -onvertir e9plicitamente un void a otro tipo +,Usar un void voidQB'Q voidQ B voidQ 'Q B )'Q*voidQ )voidQ*KKE

3,-omparaciones de igualdad o desigualdad entre voidQ )voidQTBvoidQ* Operaciones 1,-onvertir implicitamente un void a otro tipo )no void* 'Q B voidQ pro3ibidas @,0esreferenciar un void. QvE 3,&signar a void punteros a funciones o a miembros

)unteros y 5const5
Un puntero implica la intervencion de dos elementos" el puntero y el objeto apuntado )salvo que sea nulo*. !l termino reservado %const% puede tener dos significados diferentes segun el sitio que ocupe en la declaracion, haciendo constante al puntero o al objeto apuntado. :;)untero constante !l operador #Qconst#, en lugar de #Q# solo, declara al puntero como constante, esto significa que la direccion a la que apunta el puntero no puede cambiar en todo el programa. /a variable apuntada si puede cambiar. !jemplo"
int a = 5; int *con&t ptr = 'a; *ptr = -; ptr = ;=>>; #"ntero con&tante a int Cien2 &e modi$ica la variable 6rror2 intento de modi$icar el p"ntero con&tante

&l no poder modificar la direccion a la que apuntan, estos punteros se apro7iman al sentido que tiene una referencia <;)untero a constante &qui el termino %const% afecta al tipo al que apunta el puntero.

int a = 5; con&t int* ptr = 'a; ptr = ;=>>; *ptr = 6;

Cien2 el p"ntero p"ede cambiar2 &er rea&ignado 6rror. ;o &e p"ede cambiar el ob%eto ap"ntado.

!s importante observar que en el ejemplo la variable #a# no fue declarada originalmente como %const%, pero el puntero la toma como %constante%. !n este sentido, aunque la variable no puede ser modificada a traves de ese puntero, si podria serlo a traves de otro identificador, el propio nombre de la variable u otro puntero que no apunte a const. 0eclarar un puntero a const suele ser util al declarar argumentos de funciones, sirve para especificar que el argumento puntero no puede ser modificado dentro de la funcion. /a funcion standars %strcpy%, en su declaracion"
char* &trcp3 (char*p2 con&t char* <);

impide que el segundo argumento sea modificado por la funcion. /a funcion copia el contenido de #q# en #p#, por esa razon el primer argumento, que no es #const#, sera modificado. !sto no significa que al llamar a la funcion el segundo parametro necesite ser una constante, es #tomado# como constante por el puntero de la funcion. !l siguiente cuadro resume la sinta7is de punteros y #const#" Entidad E'emplo Comentario $untero constante, no puede modificar la direccion a la que apunta. $untero que apunta a const. No puede modificarse el objeto apuntado a traves de ese puntero. Notacion alternativa para puntero a const. No puede modificarse la direccion apuntada ni el objeto a traves de ese puntero.

$untero constante int Qconst ptr B RaE const intQ ptr B RaE $untero a const int constQ ptr B RaE $untero const a const const int Qconst ptr B RaE

-omo ya hemos visto, se puede asignar una variable no constante a un puntero a constante, esto por la razon de que no puede producir ningun perjuicio, un puntero a const es un puntero con restricciones. /o inverso, asignar una variable constante a un puntero que no apunte a const, no esta permitido, pues se perderia el sentido de haber restringido la operatividad de la variable y e7istiria el peligro de modificar sus datos.

)untero nulo $+Null pointer+%


&lgunos autores definen a este puntero como %aquel que no apunta a ningun sitio% y otros como %un puntero que no apunta a ningun objeto% ).tourtrup,+==>*. /a segunda definicion es mas clara, mientras que la primera puede introducir alguna confusion. 0e hecho no esta claro que podria significar que #no apuntar a nigun sitio#. !l concepto de #puntero nulo# e7iste por la

necesidad practica de hablar de un puntero que no esta ligado a ningun objeto, muchisimas funciones de las librerias de c9cKK devuelven punteros y entre los posibles valores de retorno cuentan con el de puntero nulo )ej" &trchr () *, tambien se presenta cuando solicitamos memoria dinamica, el operador #ne8# retorna un puntero nulo si no hay memoria suficiente )no todos los compiladores*. /a localidad de memoria donde esta el puntero contiene siempre algun valor )Tno e7isten celdas vaciasT cero es un valorT*. Un puntero apunta a una direccion, la indicada por el valor que almacena, por lo tanto es logico concluir que un puntero siempre apunta a algun sitio. /o que distingue a un puntero nulo no es que #no apunte a ningun sitio# sino que apunta a al"una localidad de memoria -ue6 por convencion del compilador utilizado6 no puede estar asociada a nin"un ob'eto o variable. !se valor, esa direccion #prohibida# para almacenar alli algun objeto, varia para diferentes compiladores, para Borland )y la mayoria* es la direccion : )cero* del segmento de datos. -uando el puntero apunta a la localidad : el compilador considera que su valor es #null#, o lo que es lo mismo, para este compilador #null# es equivalente a cero. !sa direccion e7iste y es el comienzo del segmento de datos,stacU, puede ser visualizado, y si no hay errores su valor deberia ser :. !7iste otro concepto que no debe confundirse con el de puntero nulo, el de %8ild pointer%. Un puntero nulo apunta a un sitio bien determinado, en cambio un #8ild pointer# puede estar apuntando a cualquier sitio, una direccion indeterminada dentro del segmento.

)untero a puntero
Un puntero almacena la direccion de un objeto, puesto que ese objeto puede ser otro puntero, es posible declarar un puntero que apunta a puntero. /a notacion de puntero a puntero requiere de un doble asterisco, #QQ#, la sola notacion suele generar un efecto de confusion considerable, y es la razon de que Mats 5enricson y !riU Nyquist, en Rules and Recommendations on C++, sugieran en lo posible reemplazar punteros a punteros por alguna otra alternativa )una clase con miembro puntero* en su ec 34. .in embargo, como se vera, el concepto en si mismo no es complejo. /a relacion entre una variable comun, un puntero y un puntero a puntero se muestra en las siguientes lineas"
int a = -; int* pt* = 'a; int**pt+ = 'pt*;

$or un lado tenemos el valor que almacena la variable #a#, el puntero #pt+# almacena la direccion de esa variable, y el puntero #pt1# almacena la direccion del puntero #pt+#. .on tres identificadores, cada uno tiene un doble aspecto" la localidad de memoria donde se asienta, y el valor que almacena en esa localidad de memoria. Declaracion e inicializacion
int a = -; int* pt* = 'a;

Direccion de memoria /alor -ue almacena en $3ipotetica% tal direccion de memoria


(0$$$6 (0$$$(0$$$6

int**pt+ = 'pt*;

(0$$$+

(0$$$-

!s interesante comprobar las diferentes salidas en pantalla de #pt1# en los siguientes casos"
co"t..pt+; co"t..*pt+; co"t..**pt+; Dmprime la direccion del propio p"ntero 'pt+'2 a<"i/ "(0$$$+" Dmprime la direccion almacenada en 'pt+'2 "(0$$$-" Dmprime el valor almacenado en '*pt* = a'2 "-".

!l comportamiento de la salida en pantalla es coherente, pues se cumplen las siguientes igualdades"


*pt+ *(*pt+) *pt* **pt+ == == == == pt*; *(pt*); a; a; !e&re$erenciacion de 'pt+' 8plicamo& '*' a ambo& lado& !e e&to 3 la linea previa &e ded"ce... ...e&ta ig"aldad

/eanse las anteriores lineas como #igualdades# )comparaciones que dan #Herdadero#* y no como asignaciones. /a estrecha relacion e7istente entre los conceptos de puntero y array, es la razon de que el asterisco doble )QQ* pueda ser interpretado indistintamente como puntero a puntero, o bien como un array de punteros.

)unteros III
)untero a !uncion
!s posible tomar la direccion de una funcion y asignarla a un puntero. Una funcion tiene una direccion, esta se encuentra dentro del segmento de codigo y marca el comienzo del codigo para esa funcion. Un puntero a funcion se declara especificando el tipo devuelto y los ar"umentos aceptados por la funcion a la que apunta, estos dos elementos del puntero deben coincidir con los de la funcion. /a sinta7is para estos punteros es la siguiente"

=.NCION !jemplo" ).N ERO A =.NCION !jemplo" (ncializacion de puntero a funcion

ype devuelto int

Nombre f+

$ar"umento>s% )int,charQ*E

ype $,Nombre% $ar"umento>s% devuelto int pf B Rf+E pf B f+E )Qpf* )int, charQ*E

)/as dos formas estan bien*

/a sinta7is de una e7presion como"


int (*p$) (int2 char*);

puede resultar poco obvia, a veces es comodo definir un tipo )con typedef* para simplificar las declaraciones. 'ambien puede declararse un array de punteros a funcion )todas deben coincidir en tipo devuelto y parametros*, un ejemplo de ambos recursos se ve a continuacion"
t3pede$ void (*pmen") (); pmen" 8rchivo [] = {'8brir2 'E"ardar2 '7errar2 'Falir4;

!n primer lugar definimos un tipo, que es un puntero a funciones. !se tipo nos permite inicializar otros punteros a funciones, en este caso un array de punteros a funciones. (nvocar un puntero a funcion no requiere de desreferenciacion y es muy similar a un llamado comun de funcion. .u sinta7is es"
;ombreGdeG$"ncion (arg"mento &); ;ombreGdeG$"ncion [indice] (arg"mento &); $"ncion #"ntero a $"ncion #ara "n arra3 de p"ntero& a

Un puntero a funcion solo puede ser inicializado utilizando la direccion de una funcion, debe e7istir concordarncia entre funcion y puntero respecto a tipo devuelto y argumentos. .e trata de un puntero especial, no requiere almacenamiento e7tra de memoria y no esta hecho para itinerar ni para aritmetica de punteros, solo para almacenar la direccion de una funcion, por medio de la cual esta es invocada.

)unteros a ob'etos
$ueden declararse punteros que apuntan a objetos instancias,de,clase de cierto tipo, la sinta7is a utilizar para la declaracion es la comun, solo es diferente el modo en que es invocado un dato o funcion miembro. -on tal fin se utiliza el operador %,V%. &l igual que punteros que apuntan a tipos definidos en el lenguaje, un puntero a objeto, luego de declarado, puede ser inicializado con la direccion de memoria de un objeto conveniente )del mismo tipo*, sea de un objeto individual o un array, tambien se lo puede inicializar como puntero nulo o a traves de otro puntero ya inicializado. .in embargo estas opciones hacen al puntero una entidad dependiente de la memoria que se haya reservado para los objetos que asignan su direccion al puntero. $ara que el puntero tenga respaldo independiente en memoria la via indicada es la reserva de memoria dinamica para el mismo. .i e7istiera una clase llamada )echa, una declaracion de puntero a objeto y la reserva de memoria dinamica tendria la forma"
)echa* ho3; ho3 = ne@ )echa;

O bien"
)echa* ho3 = ne@ )echa;

/a cantidad de memoria a reservar sera calculada de modo automatico a traves del &i5eo$ de la clase. &hora bien, el &i5eo$ de una clase no es un valor dinamico, es un valor fijo que queda establecido en tiempo de compilacion y no se modifica, de modo que si miembros de la clase necesitaran memoria dinamica esto deberia implementarse de algun modo en otro sitio, especificamente a traves de un constructor.

El puntero implicito +t3is+


-uando una funcion miembro )metodo* es invocada para un objeto, la funcion siempre recibe un parametro e7tra, no declarado, que es un puntero al objeto que invoco la funcion, ese puntero recibe el nombre generico de %this%, y puede ser usado de modo implicito o e7plicito. Heamos esto detenidamente. Una clase puede contener miembros #privados#, #publicos# o #protegidos#, para este tema solo consideraremos los dos primeros. .e trata de especificadores de acceso. Uno de los primeros pasos al manipular clases es la comprobacion de que un dato declarado como #private# no puede ser accedido de modo normal en el cuerpo de cualquier funcion. $ara poder acceder a los datos privados debemos utilizar funciones miembros de la clase donde estan declarados. .upongamos la siguiente clase"
cla&& n"m{ int 0; p"blic/ par (){0=(;4 void &et () {0=,;4 void &et+ (int a) {0=a;4 void &et, (int 0) {thi&9:0=0;4 .............. 4;

0eclara varias funciones miembro, se han definido dentro de la clase solo para simplificar la e7posicion. /a primera se distingue por ser un constructor, pero todas tienen en comun el acceder al dato privado #7# para darle un valor. No es inmediatamente obvio como es posible para una funcion miembro acceder al dato privado #7#, recordemos el modo en que una funcion cualquiera accede generalmente a datos. Una funcion comun )no miembro* puede acceder a datos" ,Wlobales" declarados fuera de toda funcion. ,/ocales" declarados dentro de esa funcion. ,$arametros" enviados por otra funcion y que son utilizados directamente )por referencia* o a traves de una copia local )por valor* !n apariencia el dato #7# no pertenece a ninguna de estas categorias.-uando declaramos un objeto perteneciente a una clase lo que tenemos es una entidad compuesta de una copia individual de los datos de la clase y de las direcciones de las funciones miembros. !s decir, luego de"
n"m "no; n"m do&;

!7isten dos objetos y cada uno tiene su propia #7#. !sa es la #7# que aparece en las funciones definidas en esta clase, sera una variable distinta para cada objeto que invoque las funciones )metodos*. /a cuestion es" Pcomo llega ese valor a la funcion, para que esta puede operar con el mismo; /a respuesta es" llega de modo implicito a traves de un puntero a ese objeto, el puntero +t3is+. 'omando como ejemplo la clase definida antes, es como si sus funciones miembro hubieran sido declaradas y definidas de este modo"
void &et+ (n"m* thi&2 int a) { thi&9:0 = a; 4

.olo que el puntero #this# esta implicito, no es necesario mencionarlo en la lista de parametros y la mayoria de las veces no es necesario mencionarlo en el cuerpo de la funcion tampoco. .e puede acceder a la variable #7# no porque sea #global# ni #local#, sino porque llega como parametro, solo que es un parametro especial, implicito. Hamos a mencionar dos casos, frecuentes, donde es necesario e7plicitar el puntero %this%" +,!7iste ambiguedad respecto a los nombres de variables. 1,Una funcion miembro retorna una referencia al objeto que la invoco. +,!l primer caso se produce si un parametro tiene el mismo nombre que un dato privado, en tal caso la ambiguedad se resuelve utilizando el nombre del parametro, y para poder acceder al dato privado sera necesario e7plicitar el puntero #this#. !s lo que sucede en el siguiente caso"
void &et, (int 0) {thi&9:0=0;4

.i el parametro tuviera otro nombre ya no seria necesario )aunque tampoco seria un error* e7plicitar el #this#. $uede parecer una complicacion innecesaria dar al parametro el mismo nombre que un dato privado, pero se trata de un recurso a veces util para detectar rapidamente, en una funcion de seteo, la relacion entre parametros y datos privados. 1, !l segundo caso se produce al retornar una referencia al objeto que invoca la funcion, se trata de un recurso frecuente en la sobrecarga de operadores, pues permite concatenar operaciones, al modo de los operadores #NN# y #VV# en iostream.h. !l siguiente ejemplo, simplificado, muestra una posible implementacion"
cla&& comple%o { do"ble 02 3; p"blic/ comple%o' operatorB= (comple%o a){ 0B= a.0; 3B= a.3; ret"rn *thi&; 4

/a funcion retorna una referncia a un %complejo% a traves del puntero #this#. /os datos #7# e #y# del objeto que invoca la funcion no necesitan un #this# e7plicito, esto ocurriria en caso de que el parametro fuera, por ej, un entero #7# o #y#. /a linea final, que e7plicita el retorno de un puntero al mismo objeto que la invoco, es necesaria para detalles relacionados con la concatenacion de operadores, la modificacion de los datos privados ya se ha hecho en las dos lineas anteriores.

.tilidad de los punteros


Aritmetica de punteros
.on posibles ciertas operaciones con punteros y los operadores de suma y resta ),,K,,,,KK*. .iendo #'# el tipo a que apunta el puntero, el siguiente cuadro sintetiza las distintas posiblidades y el tipo de resultado generado"

Operacion )untero a )untero 7; entero mas entero

Resultado

Comentarios

.i el puntero apunta mas alla del limite superior del $untero a ' array el resultado es no definido .i el puntero apunta mas alla del limite inferior $untero a ' del array el resultado es no definido ,,,,,,,,,,,,, !ntero No permitido !l resultado indica el numero de elementos ' entre los dos punteros

)untero a entero

menos

$untero mas puntero )untero 7; puntero )untero a menos puntero a

/os punteros son direcciones de memoria pero la aritmetica de punteros no es una simple suma o resta de direcciones. !stas operaciones estan adaptadas especialmente para tratar con arrays, de ahi que incrementar en + el valor de un puntero no apunte a la pro7ima direccion de memoria, sino al pro7imo elemento de un array. !l unico caso donde #pro7ima direccion# es igual a #pro7imo elemento# es el caso de un array de caracteres, los restantes tipos )por lo menos los propios del lenguaje* ocupan mas de un byte por elemento. )untero 7; entero !s el tipo de operacion mas frecuente con punteros, especialmente porque el incremento del puntero en + permite recorrer un array elemento por elemento. 5ay dos modos de realizar esto, el primero consiste en modificar el valor del puntero, y el segundo en direccionar el elemento igual a FpunteroK enteroG, procedimiento que tiene la ventaja relativa de no modificar el valor inicial del puntero, que seguira apuntando al mismo elemento del array. $or ejemplo"
long H [-] = {,52,-5+-25-,5I-2,5-4; long* pH = H; int a; #rimer modo 9999999999999999999999999999999999999 $or {a=(;a.-;aBB) { print$("1ld"2*pH); pHBB; 4 Feg"ndo modo99999999999999999999999999999999999999 $or (a=(;a.-;aBB){ print$("1ld2 *(pHBa));

!n ambos casos se obtiene el mismo resultado, pero al salir del bucle el estado del puntero sera diferente segun la modalidad adoptada. !n el primer caso el puntero estara apuntando fuera del array )pUK3*, en el segundo el puntero seguira apuntando al comienzo del array, pues las direcciones sucesivas se tomaban del valor temporal de )pUKa*, sin afectar al valor del puntero. !n el bucle de la segunda modalidad es importante la presencia del parentesis, la notacion QpUKa tendria un efecto por completo diferente, el operador #Q# tiene mayor precedencia que #K#, por lo tanto se tomaria siempre el elemento QpU )el primer elemento del array* y luego se le sumaria #a#. 0ebe quedar claro que el puntero se puede incrementar con cualquier valor entero, hay rutinas que necesitan tomar un elemento por medio, en tal caso dentro de un bucle tomariamos los sucesivos valores )pK1*. O si necesitaramos obviar los primeros #n# caracteres de una cadena, y copiar el resto en un buffer, podriamos escribir" &trcp3(b"$$er2 cadBn); /a suma de enteros a punteros tiene muchas aplicaciones, en especial si se combinan con las librerias standard de - y -KK. !n la tabla previa se menciona que de darse el caso de desbordar el limite del array el resultado de la suma )o resta* es #indeterminado#. !sto puede depender en parte de cada compilador, pero como norma general lo que es indeterminado no es el valor,resultado del puntero )la direccion que almacena*, sino el valor almacenado en tal direccion. !s decir, dado un puntero,resultado #pr#, por mas que ese valor desborde el array al que apuntaba, #pr# sera previsible mientras que #Qpr# no lo sera, se dice que su valor es #indefinido#.

)untero ; puntero
!sta operacion da como resultado no un puntero sino un valor entero. !s necesario que ambos punteros sean del mismo tipo, en caso contrario se producira un error en tiempo de compilacion. /a resta de punteros tiene la propiedad de que su valor es independiente de los tipos implicados, es decir" dado un puntero #p+# y otro #p1# que apuntan respectivamente a los elementos #n# y #m# de un array, el valor entero de #p+,p1# sera igual a #n,m#, independientemente del tipo a que apunten los punteros. !l valor entero del resultado debe ser interpretado como #numero de elementos )del mismo tipo que los punteros* entre ambos punteros#, y no debe ser tomado como una simple resta de valores de memoria. $or ejemplo"
int t [] = {-52,-5252,5-2,-54; int* pt* = 't[*]; int* pt+ = 't[-]; int re& = pt+9pt*; en ve5 de 't[+] &e p"ede tB+;

!l valor de #res# sera @, mientras que en termino de direcciones de memoria la distancia entre ambos punteros es de <, pues cada entero ocupa 1 bytes. No es necesario involucrarse con demasiados detalles de lo que sucede en memoria, los punteros y su aritmetica estan adaptados para tratar con direcciones de modo implicito. /a resta de punteros no es tan frecuente como las operaciones de incremento pero puede prestar usos valiosos, sobre todo en relacion a cadenas de caracteres y junto a librerias standard de funciones. .upongamos que necesitaramos una rutina que e7traiga una subcadena de una cadena

dada, y que la subcadena estuviera determinada por dos caracteres delimitadores, por ej #ch+# y #ch1#, el esquema de una funcion de e7traccion seria el siguiente" +,.e setean dos punteros )p+ y p1* apuntando a los delimitadores #ch+# y #ch1#. ?uncion que realiza &trchr(). 1,!n un bucle se e7traen )p+,p1* caracteres a partir de #ch+#. !n general se recomienda que la aritmetica de punteros se realice en el nivel mas simple posible. /a principal fuente de error proviene de desbordar los limites )inferior o superior* del array al que apunta el puntero. !stas dificultades no se presentan si se esta tratando con mapeo de memoria, flujos de bytes, analisis sintactico a nivel compilador o rutinas similares de bajo nivel, pero cuando los datos presenten mayor nivel de estructuracion seran necesarias mayores precauciones.

Itinerar en un array
.upongamos una cadena #cad# y un puntero que apunta a esa cadena.
char cad [] = "hola"; char * ptr = cad;

!l puntero #ptr# apunta al primer elemento de #cad#. .i ahora incrementamos el puntero"


ptrBB;

este apuntara a cadF+G, es decir el segundo byte de cad, y si sacamos en pantalla #p# y #Qp# se vera lo siguiente"
print$ ("1&"2 p); print$("1c"2 *p); &ale "ola" &ale 'o'

-omo hemos visto antes, print$() saca en pantalla todo lo que haya desde el char al que apunta el puntero recibido como parametro 3asta el primer #D:# que encuentre. /a e7plicacion de lo sucedido es la siguiente" #p# contiene )como cualquier variable* un valor, este valor es una localidad de memoria, por ej :7fff1, al incrementar el puntero con pKK lo que hacemos es incrementar el valor que contiene, por eso pasara de :7fff1 a :7fff@. .i el puntero hubiera sido tipo entero, el incremento #pKK# habria sumado en 1 la localidad apuntada ):7fff3*, pues un entero ocupa 1 bytes de memoria. !l mecanismo es simple y muy eficaz para itinerar a traves de un array, pero no solo eso, tambien nos permite itinerar por cualquier zona de memoria y es el metodo mas comodo para hacerlo.

8apear localidades de memoria


-asi siempre se mencionan a los punteros en relacion a arrays, pero un puntero puede operar de modo totalmente independiente de cualquier array, precisamente para itinerar libremente por regiones de memoria, segun .troustrup )+==A*, "la implementacion de p"ntero& tiene por
$inalidad mapear directamente lo& mecani&mo& de direccionamiento de la ma<"ina en <"e &e e%ec"ta "n programa ".

Heamos un ejemplo. .upogamos que nuestro programa opera en modelo small )como la mayoria de los ejemplos dados* y que queremos observar el estado del segmento de datos,stacU durante la ejecucion del programa. Una funcion como la siguiente podria cumplir esa funcion"

int $"ncion() { char *tt=(; "n&igned char ch; int a2$il2col; $or (a=(;a.+56;aBB) { ch = *(ttBa); ch=*(ttB(0$$((Ba); col = a1*6; $il = a *6; goto03(col*,B+-2$ilB-); print$("1(+J"2ch); i$ (ch.,+) ch=-6; goto03(colB+2$ilB-); print$("1c"2ch); 4 ret"rn (;4

8p"nta al comien5o del &egmento de dato& =n&igned2 para no lidiar con valore& negativo&

#ara ver el $inal del &egmento &eria/

Kepre&entacion he0adecimal Fi ch .,+ &e reempla5a con p"nto& Kepre&entacion a&cii

/a funcion saca en pantalla los primeros 1A< bytes del segmento de datos,stacU. /os detalles del bucle de impresion son para que la salida sea similar a la de un editor he7adecimal, en una columan los caracteres ascii, e7cluyendo a aquellos cuyo valor es menor a @1 ):71:*, en realidad muchos de estos caracteres se pueden imprimir bien, mientras que es mejor evitar algunos como >,4,+:,+@, pero se han evitado todo los menores a @1 para simplificar el codigo. !n otra columna se e7hibiran los valores he7adecimales de esos caracteres. !l final del segmento de datos es muy interesante pues almacena los valores de las variables locales, por esta causa sufre importantes cambios con cada llamado a funcion. $ara observar tal sector, con el codigo anterior, basta con reemplazar la primera linea debajo del bucle por la indicada en el comentario. !l esquema de la funcion, aplicada a lectura de ficheros )archivos*, podria ser de utilidad en una salida a pantalla de un editor he7adecimal.

)aso de parametros en !unciones


$or default las variables declaradas dentro de una funcion no estan disponibles para otras funciones. -uando necesitamos que una funcion acceda a datos de otra el principal recurso es el paso de argumentos. !l paso de argumentos se hace principalmente a traves de la pila )stac&*, un bloque de memoria especializado en el almacenamiento de datos temporales. !l espacio total disponible para uso de la pila varia segun el modelo de memoria utilizado por el programa )aparte de las limitaciones de hard8are*, si nuestro programa utiliza el modelo #small# de memoria el espacio total para uso de pila y datos sera de <3 Cb. -uando se guardan en la pila mas valores de los que caben se produce un #stacU overflo8#, un desborde de pila. /as funciones recursivas trabajan haciendo una copia de si mismas y guardandola en la pila, motivo por el cual no es raro encontrar desbordes de pila provocados por recursiones mal calculadas. 5ay muchos motivos para utilizar la pila del modo mas economico posible, y los punteros cumplen una gran utilidad en este caso. Un parametro puede ser pasado a una funcion de dos modos diferentes" por valor y por re!erencia. $asarlo por valor implica que la funcion receptora hace una copia del argumento y trabaja con ese doble del original, cualquier modificacion realizada en la variable,copia no producira ningun cambio en el parametro enviado. !n cambio al pasar un valor por referencia

estamos pasando la direccion de memoria del mismo argumento, en este caso no hay otra #copia# del mismo, el dato es uno y el mismo para las dos funciones, la que envia el dato y la que lo recibe. Una variable comun puede ser pasada por valor o por referencia. !n el siguiente ejemplo la funcion #f # recibe dos parametros pasados por la funcion #principal#, el primero es pasado por valor y el segundo por referencia. !n el primer caso #f # incrementara el valor de la variable #a# que es una copia local del argumento #7#, ese incremento afectara a #a# pero no a #7#. !n el segundo caso la variable #y# es pasada por referencia, por lo tanto el incremento operado sobre #b# es al mismo tiempo un incremento de #y#. 'anto la variable #y# de la primera funcion como #b# estan asociadas a la misma localidad de memoria )hay dos nombres para una misma localidad de memoria*.
void $ (int a2 int' b) { aBB; bBB; 4 999999999999999999999999999 void principal() { int 0 = *; int 3 = *; $(023); ...................

Un array en cambio siempre es pasado por referencia, la funcion que recibe el parametro recibe un puntero al elemento inicial del array. $or ejemplo"
int 0&trlen (char* &tr) { int a=(; @hile (*&trBBL=() { alternativa99: B;4 aBB; 4 ret"rn a; 4 99999999999999999999999999999 void principal () { char cad[] = "hola"; print$ ("1d"2 0&trlen(cad)); .......................................

@hile (&tr[a]L=() {aB

!n el ejemplo, la funcion 7strlen)* nos da el largo de un array de caracteres buscando la posicion del #D:# de fin de cadena, y la funcion #principal# sacara ese valor entero en pantalla. Observese la sinta7is alternativa para el bucle, en un caso anotamos #str# como puntero y en el otro como #array#, ambas notaciones son intercambiables. &l pasar un array por referencia, la funcion receptora solo recibe la direccion inicial del array, es decir 1 bytes, por lo tanto pasar un array de @: CB consume menos recursos de stacU que pasar una variable de tipo long )pasada por valor*, que requiere 3 bytes. !l principal inconveniente de pasar un parametro por referencia radica en la posibilidad que tiene la funcion receptora de alterar

todos los datos del parametro, por esta causa es frecuente que, en la declaracion de la funcion, tal parametro se declare como %const% para evitar la corrupcion accidental de ese dato.

Reserva de 8emoria Dinamica


!n primer lugar recordemos que es la #memoria dinamica#. 5ay tres formas de usar la memoria en -KK para almacenar valores" :;8emoria estatica( !s el caso de las variables globables y las declaradas como #static#. 'ales objetos tienen asignada la misma direccion de memoria desde el comienzo al final del programa. <;8emoria automatica( Usada por los argumentos y las variables locales. -ada entrada en una funcion crea tales objetos, y son destruidos al salir de la funcion. !stas operaciones se realizan en la pila )stacU*. ?;8emoria dinamica( 'ambien llamado #almacenamiento libre# )free store*. !n estos casos el programador solicita memoria para almacenar un objeto y es responsable de liberar tal memoria para que pueda ser reutilizada por otros objetos. /a operacion de reservar y liberar espacio para variables globables, estaticas o locales son realizadas de modo implicito por el programa, la unica modalidad que requiere mayor atencion por parte del programador es la de reservar memoria en forma dinamica. !l papel de los punteros en relacion a la memoria dinamica es muy importante, por la razon de que al pedir, al sistema operativo, una cantidad determinada de memoria dinamica para un objeto, el sistema nos retorna un puntero que apunta a esa zona de memoria libre, la respuesta dependera de si hay o no tanto espacio como el solicitado. a* .i hay suficiente memoria se retorna un puntero que apunta al comienzo de esa zona de memoria. b* .i no hay suficiente, retorna un puntero nulo( !n -KK los operadores usados para requerir y liberar memoria dinamica son ne2 y delete. /a sinta7is es la siguiente" Hariable individual &rray de elementos individuales eserva de memoria dinamica /iberacion de memoria reservada intQ a B ne8 int delete aE intQ a B ne8 int FnGE delete FG aE

Nota* las primeras versiones de 'urbo-KK no admiten la linea %delete F G a% con corchetes vacios, para liberar memoria dinamica de un array requiere que e7plicitemos cuantos elementos hay que borrar, e7plicitando este valor entre corchetes.!n la version 'urbo-KK@.: esta caracteristica de la sinta7is standard ya se encuentra implementada. /as ventajas de utilizar memoria dinamica se valoran mejor en comparacion con las caracteristicas de la reserva estatica de memoria.

eserva estatica de memoria -reacion de objetos

eserva dinamica de memoria

/os objetos locales son creados al entrar en /a memoria se reserva e7plicitamente mediante la funcion que los declara. /os globales son el operador ne2( creados al iniciarse el programa. /os objetos locales se destruyen al salir /os objetos necesitan ser destruidos de la funcion en que han sido e7plicitamente, creados. /os con el operador delete. globales, al salir del programa. &l reservar memoria estatica para un array el valor del indice debe ser un valor constante. !j" int n F1:GE int n Fvariable no constGE 99no permitido !l indice de un array puede ser un valor variable, de modo que la cantidad de memoria reservada por una linea de codigo puede variar en tiempo de ejecucion )runtime*. !j" intQ n B ne8 int Fvariable no constG 99correcto

0uracion de los objetos

(ndice de arrays

/a estrecha relacion que e7iste entre arrays y punteros e7plica que la solicitud de memoria dinamica para un array culmine en la devolucion de un puntero, una vez que ha sido reservada la memoria suficiente operamos sobre el puntero directamente, de modo muy similar a como operamos con un array. /os mecanismos de bajo nivel que implementan el uso de memoria dinamica son bastante complejos y no nos detendremos en ello. 0esde el punto de vista del programador, la principal fuente de errores se deriva de una mala coordinacion entre operadores ne2 y delete, sea que olvidemos liberar la memoria que ya no utilicemos, o que intentemos borrar, o utilizar, un objeto ya borrado

)roblemas I
)unteros no inicializados
/a sola declaracion de un puntero, independientemente del tipo )type* a que apunte, no reserva en memoria mas espacio que el necesario para almacenar un valor que representa una direccion de memoria" es decir 1 o 3 bytes. /a siguiente linea de codigo tiene ese efecto.
char * ptr;

0eclarar un puntero de ese modo no es ningun error, el error es olvidar lo siguiente" +,2ue se trata de un puntero #no inicializado#, que puede estar apuntando a cualquier localidad de memoria, tal vez alguna en la cual sea erroneo escribir algun dato )por ej" el comienzo del segmento de datos*.

1,2ue no estamos reservando ningun espacio e7tra para asociar un array, una estructura o un objeto a ese puntero. !s suficiente continuar la linea anterior con una de las siguientes"
*ptr = 'a'; &trcp3 (ptr2 "hola");

para cometer un error, es probable que al final del programa aparezca el mensaje #Null pointer assignment#, indicando que se ha sobreescrito una zona #prohibida# del segmento de datos. /os detalles de tal mensaje de error se tratan aparte, por ahora lo importante es insistir en que un puntero no inicializado es peligroso, pues apunta a una localidad de memoria indeterminada, y que es un error setear una localidad de memoria )desreferenciando el puntero* con un valor sin tener claro de que localidad de memoria se trata, o a que variable se encuentra ligada. .i el puntero hubiera sido inicializado por ej, apuntando a un array, entonces el primer problema, adonde apunta, estaria solucionado. $ero aun podriamos olvidar el segundo, el espacio de memoria reservado debe ser suficiente. $or ejemplo"
int main() { int 0 = -; e%emplo char cad [] = "hola"; para 'cad' char* ptr = cad; &trcp3(ptr2 "ca&a"); 'hola' &trcp3(ptr2 "C"en dia"); ........................etc

Mariable c"3o valor &era de&tr"ido en e&te Ke&erva e&tatica de 5 b3te& (- ma& * del 'N(') ptr ap"nta a cad[(] bien2 'ca&a' tiene - b3te& de te0to2 no e0cede a OalL2 e&ta cadena e0cede la capacidad de 'cad'.

/a ultima cadena se copiara de todos modos en la direccion apuntada por #ptr#, desbordarndo la capacidad del array #cad# para almacenar ese dato, como consecuencia el valor de la variable #7# sera destruido. .e trata de un error que no es dificil de cometer, la regla que podemos seguir es tratar al puntero como un #alias# del array al que apunta, los problemas de desbordar la capacidad del array, escribiendo por ejemplo cadF>GB#a#, son e7actamente los mismos que los de hacer lo mismo con el puntero, solo que por alguna razon es mas facil cometer el error con el puntero olvidando la capacidad del array al que apunta. !n el siguiente ejemplo se visualizara lo que sucede con las localidades de memoria implicadas cuando se produce un error de sobreescritura como el antes mencionado.
int main () { int 0 = 52 3 = -2 5 = ,; char cad[] = "abcde"; char* ptr = cad;

&trcp3(ptr2"Pa&ta l"ego");

-omo puede observarse se han perdido los valores originales de las tres variables enteras. /as consecuencias concretas de sobreescribir variables dependen enteramente de lo que haga el resto del programa, en todo caso se trata de un error. $or lo tanto es importante evitar la presencia de punteros no inicializados y que por ello apuntan a una zona de memoria indeterminada, en ingles generalmente se los denomina #8ild pointers# )punteros salvajes*.

)unteros y literales de cadena


Un literal de cadena es un conjunto de caracterese encerrados entre comillas, por ejemplo la cadena %5asta luego% del ejemplo anterior. /os literales de cualquier tipo son tratados como valores constantes y son almacenados, a diferencia de las variables locales, cerca del comienzo del segmento de datos. -on la linea siguiente"
char* ptr = "hola";

se crean dos entidades, no una. $or una parte se reservan 1 bytes para el puntero #ptr# )para almacenar una direccion*, pero tambien es creada otra entidad, un literal de cadena, que es constante, su contenido aqui es #hola# y no es modificable en el transcurso del programa. /os bytes reservados por esta linea de codigo son" ,1 para que el puntero almacene una direccion )aqui la direccion donde se encuentra el literal #hola#* ,A para el literal. !s importante comprender que un #literal de cadena# es una entidad diferente a un array de caracteres o a un puntero a charQ, es un valor constante y su contenido se almacena en un sector especial del segmento de datos, en la parte inicial del mismo. !n los casos en que un puntero es inicializado con un #literal de cadena# el error es tomar el puntero e intentar copiar algo desreferenciandolo. &lgunos compiladores daran un mensaje de error en tiempo de compilacion, otros mas antiguos pueden permitir tal copia. /o recomendable, cualquiera sea el compilador, y permita o no modificar el valor apuntado, es no intentar modificar el contenido apuntado por #ptr# en ningun caso. .i se necesita modificar el contenido lo mejor es copiar el literal en un array, reservando la memoria suficiente, y operar sobre el array. /igar un puntero a un literal de cadena no es un error, si lo es el intentar modificar un valor que debe ser tratado como constante. -uidando estos detalles, declarar un puntero a literales puede ser muy comodo para manejar cadenas constantes, como las que que conforman menus, en estos casos un array de punteros es un buen recurso.
char* men"*[] = {"8rchivo"2 "8brir"2 ";"evo"2 "E"ardar"2 "E"ardar como..."2 "Falir"4;

!sto es mas facil de manejar que un array multidimensional, con %menuFnG% accederemos a cada una de las cadenas, en un estilo muy similar al de lenguajes que conciben las cadenas de

caracteres como un tipo )type* propio y no un array. .i las cadenas necesitaran ser modificadas habra que implementarlo de otro modo, por ejemplo asignando memoria dinamica para su almacenamiento.

El mensa'e +Null pointer assi"nment+


Un progreso importante en el manejo y comprension de bugs y mensajes de error es nuestra capacidad de reproducirlos de modo previsible. $or alguna razon el mensaje %Null pointer assignment% es uno de los que mas cuesta reproducir, posiblemente porque e7iste cierta confusion en torno a la nocion de #puntero nulo#. /a traduccion literal del mensaje de error seria" %asignacion de puntero nulo%, o en otros terminos" %se ha asignado un valor a un puntero que es nulo%. & continuacion veremos en detalle que significa esto, cuales son los pasos que lleva a cabo un compilador )aqui 'urbo-KK+.:+* para emitirlo, como podemos reproducirlo de manera controlada, y por ultimo que cuidados podemos tener para evitarlo. &ntes que nada recordaremos que es un puntero nulo" es un puntero que apunta a un sitio donde no debe )pero si puede* estar almacenado ningun dato, por asi decir, se trata de una zona #prohibida#, se puede leer pero no setear valores alli. 0e este modo, cuando una funcion que retorna punteros retorna un puntero %Null%, se usara esto como significando #nada#. $or ejemplo" la funcion strchr)* busca un caracter dentro de una cadena, si lo encuentra retorna un puntero a ese caracter, si no lo encuentra retorna un puntero nulo. &hora bien, un puntero apunta siempre a algun sitio, y el #puntero nulo# no es una e7cepcion. -on un compilador Borland y modelo de memoria small o medium un puntero nulo apunta a la direccion : del segmento de datos, es decir 0.""::::. !scribir alli un valor que no sea : garantiza la presencia del mensaje de error, pero no es la unica localidad de memoria que lo produce. /as condiciones para producir el mensaje de %Null pointer assignment% son las siguientes" +,!l modelo de memoria utilizado por el programa es .M&// o M!0(UM. 1,.e sobreescribe algun valor del comienzo del segmento de datos,stacU. !s decir el intervalo compuesto por 3 bytes con valor : )cero* mas el copyright de Borland.

.i se modifica algun valor a partir de la localidad 0."":7@d, donde comienza el mensaje %Null pointer...% ya no se produce el mensaje de error, de hecho solo las localidades resaltadas con color pueden producirlo. -on mas e7actitud habria que decir que no es el hecho de #escribir# alli lo que genera el mensaje de error, sino el hecho de que, al terminar el programa, alguno de esos bytes

contenga un valor diferente al que se observa en la imagen. $or lo tanto si uno sobreescribe el mismo valor que tiene, o bien lo altera pero antes de salir del programa lo vuelve a reestablecer, el mensaje de %Null pointer assignment% no se produce. !sto ultimo solo a titulo informativo, no es recomendable de ningun modo intentar operar sobre esas localidades de memoria. /os siguientes ejemplos ilustran algunos modos de generar el mensaje de error. !jemplo N"+ , 0esreferenciacion de un #8ild pointer#
int main () { char* p; *p = 'a'; ret"rn (; 4

No esta determinado a donde apuntara #p#, pero en los ejemplos observados apunta siempre a :7:::: o :7:::c, ambos bytes #prohibidos#, al darle el valor #a# modifica el comienzo del segmento y aparece el mensaje de error que estamos estudiando. !jemplo N"1 , No requiere comentarios, sucede lo mismo que en el ejemplo anterior.
Qincl"de .&tring.h: int main() { char *p; &trcp3 (p2 "@0&%H@e"); ret"rn (; 4

!jemplo N" @ , Olvidar que una funcion ha retornado un puntero nulo


Qincl"de .&tring.h: int main () { char* p; char ch = 'a'; char cad[] = "%orge"; p = &trchr(cad2 ch); ................... *p = '0'; p"ntero n"lo2 ....................etc

b"&camo& 'ch' dentro de 'cad'. 7omo 'ch' no e&ta en 'cad' &trchr() retorno "n <"e ahora e& de&re$erenciado 3 e&crito.

!n este ejemplo hemos invocado una funcion, la misma retorno un puntero nulo y luego, y sin redireccionar el puntero, le damos un valor, como resultado se escribe #7# en el primer byte del segmento provocando el mensaje de error. &lgo similar ocurriria si al solicitar memoria dinamica #ne8# retornara un puntero nulo, y operaramos con el mismo sin antes comprobar el e7ito de la solicitud. !7isten muchisimos modos de producir el mensaje de error, pero todos se basan en lo mismo. Una observacion mas" el mensaje de error nos avisa que hemos escrito en un puntero #nulo#, en este conte7to eso significa un puntero que apunta a :7::::, si apuntara a :7:::+ ya no seria un

puntero nulo. .in embargo hemos visto que no es esa la unica localidad de memoria que produce el mensaje de error, se trata mas bien de un intervalo de 3A bytes, desde :7:::: hasta :7::1c, por lo tanto no es solo la #asignacion de un puntero nulo# la que provoca el mensaje, aunque asi lo da a entender %Null pointer assignment%, por lo menos asi sucede en los compiladores Borland.

+Dan"lin" pointers+
!ste tipo de problemas suscita muchas preguntas en las diversos foros )o ?aq#s* sobre - y -KK, y se presenta con frecuencia en funciones que retornan un puntero. /a causa del problema es esta" la funcion retorna un puntero que apunta a una variable o array declarados como locales, al salir de la funcion todas las variables locales son #deallocated#, se pierde la cone7ion entre direccion de memoria y variable, la zona de memoria que utilizaban es liberada, por lo tanto el puntero )al salir de la funcion* apunta a una #zona liberada#, no ligada con ningun array o variable. /a siguiente funcion #f+# reproduce el problema"
char * $*() { char b"$$er[*+8]; local co"t .. "6ntre &" nombre/ "; cin.getline( b"$$er2 *+8 ); ret"rn b"$$er; 4 int main(){ char* ptr; .................. ptr = $*(); 'b"$$er' $+();

Ke&erva de memoria e&tatica para variable

Ketorna como p"ntero de variable local

Ke&to del codigo a<"i 6l p"ntero ptr recibe la direccion de >lamado a "na $"ncion '$+' c"al<"iera

!l puntero #ptr# recibira la direccion #correcta#, la misma en que estaba almacenada la cadena #buffer#, el problema es que #buffer#, al ser declarada como local, pierde su localizacion de memoria. !l rol que juega la stacU )pila* en el llamado a funciones se ilustra en el siguiente grafico"

Hamos a comentar paso a paso la relaciXn entre stacU, funciones y el cXdigo anterior. (, &l comenzar el programa se hace lugar en la pila para albergar todas las variables locales de #main#, este lugar se encuentra al final del segmento de pila y solo serY liberado al terminar el programa. 5asta ese momento los datos locales de las restantes funciones #no e7isten#, en el sentido de que no tienen localidades de memoria donde almacenar un valor. 0istinto es el caso con las variables declaradas como #static#, pero estas se encuentran en la parte baja de la pila y no producen el problema que estamos viendo. ((, /a funciXn main ) * llama a la funciXn #f+#. -omo los valores de las variables de main no se pierden hasta el final del programa, en la pila se hace lugar, debajo de estos valores, para almacenar las variables locales de #f+#, en tZrminos de ensamblador dir[amos #la pila crece )hacia abajo* seteando un nuevo valor de B$ )bass pointer* y .$ )stacU pointer*#. !n esas localidades de memoria, estarYn los valores de #f+# hasta que salgamos de la funciXn. (((, .alimos de la funciXn f+ retornando un puntero a una variable local. /o que el puntero retorna es, obviamente, una direcciXn de memoria, esa direcciXn se mantiene, no es borrada ni se pierde. !l problema no es el puntero, el problema es que se pierde la variable local( P2ue sucede con las variables locales al salir de la funciXn; Mientras no se llame a otra funcion sus valores pueden perdurar, pero no es algo que un compilador garantice. (H, /lamamos a otra funcion. !n ese momento las localidades de memoria asociadas a las variables de la anterior funcion #f+# seran sobreescritas por las variables locales de la nueva funcion llamada #f1#. Nuestro puntero a la variable local de f+ seguira apuntando a la misma localidad de memoria, pero su contenido sera indeterminado, y sera muy peligroso usarlo para cualquier proposito )a menos que sea reasignado*. /a regla practica seria esta" no retornar nunca un puntero -ue apunte a una variable declarada como local( $ero entonces, Pque camino seguir para retornar un array o puntero de modo seguro desde una funcion; !n la literatura e7istente sobre el tema se analizan y recomiendan tres posibles soluciones, todas apuntan a preservar el valor de la variable, evitando que sea #local#" +,0eclarar a la variable #static# 1, eservar memoria dinamica para la variable dentro de la funcion @, etornar el valor utilizando un parametro de la funcion llamadora. .e analizaran cada una de las soluciones, anticipando que las tres son eficaces en evitar el problema de punteros #dangling#, solo se trata de evaluar sus efectos. +,&l declarar una variable como #static# le estamos reservando un sitio especial dentro del segmento que no sera alterado por el flujo general del programa, ese sitio es en la parte baja de la stacU, lejos de la parte alta donde se produce todo el movimiento de variables locales de las distintas funciones. $ara esto basta con anteponer #static# a la declaracion de la variable"
&tatic char b"$$er[*+8];

!l unico inconveniente es que esa zona de memoria no sera liberada en todo el transcurso del programa, la cantidad de bytes reservados determinara si este recurso es demasiado costoso o no.

1, eservar memoria dinamica dentro de la funcion llamada. !n nuestro ejemplo seria"


char* b"$$er = ne@ char [*+8];

&qui sera responsabilidad del programador liberar la memoria reservada, en caso contrario se produciran #fugas de memoria# )memory leaUs*, es decir, memoria fuera de uso que no puede ser reutilizada para almacenar nuevas variables. .e trata de un tema tecnicamente complejo, al punto de que muchos compiladores no son enteramente eficaces en la liberacion de memoria reservada dinamicamente )tardan en hacerlo*, e7iste soft8are #recolector de basura# )garbage collection* cuya funcion es liberar zonas de memoria a las que ya no puede acceder ninguna variable en tal punto de un programa. @,0evolver el valor a traves de un parametro. /a variable de retorno no se declara dentro de la funcion que retorna, sino en la funcion que llama. $or ejemplo"
void $* (char* b"$$) { .................. 4 int main () { ............... char b"$$er[*+8]; $*(b"$$er); .................etc2

No es necesario retornar e7plicitamente la variable pues el parametro ha sido pasado #por referencia#, #buff# de #f+# apunta a la misma localidad que #buffer# de #main#, y pueden ser tratados como un mismo puntero. !l unico defecto del metodo radica que puede disminuir ligeramente la legibilidad del codigo, la funcion retorna un puntero pero de modo disimulado, el tipo )type* de la funcion no nos informa nada al respecto. /as funciones de las librerias de - y -KK utilizan en general las dos ultimas alternativas, )1* y )@*.

)roblemas con punteros II 8emoria dinamica


/as operaciones de reservar memoria dinamica y liberarla, con ne8 y delete, estan enteramente en manos del programador, esto proporciona gran fle7iblidad de recursos pero tambien oporturnidad para diversos tipos de errores. -ada vez que aparece el operador ne8 en relacion a un objeto deberia haber una aplicacion del operador delete a ese mismo objeto. /os problemas suelen generarse por dos modalidades de error" +,No liberar la memoria dinamica reservada para un objeto. 1,(ntentar borrar, o desreferenciar, un objeto ya borrado. +,Memoria no liberada /a memoria reservada dinamicamente necesita ser liberada de modo e7plicito con delete. .i e7iste un objeto que ya no usamos, y que fue almacenado dinamicamente, nos encontramos frente

a una #fuga de memoria# )memory leaU*, el caso mas critico se presenta cuando ya no es posible acceder al objeto, pues no sera posible borrarlo. 0urante la ejecucion del programa el numero de #fugas de memoria# puede multiplicarse hasta agotar los recursos disponibles. Un caso muy simple donde no es posible acceder al objeto para liberar memoria es el siguiente" int f )int a* I charQ p B ne8 char FaGE return :E J /a funcion no hace nada interesante, es simplemente el esquema de un error posible. /a memoria reservada para el puntero #p# no ha sido liberada al salir de la funcion, la direccion apuntada por #p# se pierde, y no sera posible liberar esa memoria en ningun sitio. !s el modo mas simple de producir una fuga de memoria. 5abria sido necesario agregar la linea" delete FG pE antes de salir de la funcion para que ese monto de memoria hubiera sido liberado. Mientras que es tecnicamente posible reservar memoria en una funcion y liberar esa memoria en otra funcion, se considera una practica riesgosa, por la posiblidad de olvidar quien tiene la responsabilidad de liberar memoria. Una posible solucion es reservar memoria para el objeto en la funcion llamadora, pasar el objeto como parametro )por referencia* y retornarlo, asi la responsabilidad de reservar y liberar memoria respecto al objeto estara en manos de una misma funcion. !l esquema seria el siguiente" int f )charQ c* I ............... return :E J int main)* I int b B @3E charQ t B ne8 charFbGE f)t*E ............... delete FG tE ............... Otro modo de provocar problemas es reservar memoria dinamica por segunda vez para un puntero, antes que haya sido liberada la primer reserva. $or ejemplo" void f )int a, int b* I charQ p B ne8 charFaGE ...................... p B ne8 charFbGE

..................... &qui la memoria reservada por el primer uso de ne8 ya no podra ser liberada, pues se ha perdido su direccion. 'oda segunda asignacion del puntero #p# sin antes liberar la memoria dinamica asociada a el, producira fugas de memoria. Otra variacion del mismo problema es el siguiente" void f )int a* I charQ p B ne8 charFaGE charQ q B ne8 charFaGE ...................... p B qE ..................... delete FG pE delete FG qE J !ste esquema de error es importante pues, como se vera mas adelante, se presenta en forma velada en problemas con constructores de objetos. !l error es reasignar el puntero #p# antes de liberar la memoria por el reservada. !sto tiene dos consecuencias negativas" +, /a memoria reservada originalmente por #p# no podra ser liberada. 1, /a memoria reservada por #q# sera liberada dos veces )muy problematico*. Un modo de fallar en liberar acertadamente la memoria reservada con ne8, en relacion a un array, consiste en aplicar el operador delete, olvidando los corchetes entre el operador y el nombre del puntero. int f )* I charQ p B ne8 charF+::GE ........................ delete pE 99error, solo libera un elemento de #p# delete FG pE 99bien, libera el array apuntado por #p# 1,Operar con un objeto ya borrado /a segunda familia de problemas se produce por intentar desreferenciar o usar un puntero al cual ya se ha aplicado el operador delete. !l principal recurso para evitar este problema es )una vez aplicado el operador delete* setear este puntero a NU//, esto protege contra posteriores usos equivocados de delete, pues por convenciXn la aplicaciXn de delete a un puntero nulo no tiene ning\n efecto. 'ambiZn es un error desreferenciar un puntero al que se ha aplicado delete sin antes asignarle una nueva direcciXn. /a razXn es que el puntero esta apuntando a alguna zona que almacena valores indeterminados, sobrescribir all[ puede destruir datos pertenecientes a otras variables o a otras reservas dinYmicas. /a soluciXn es reasignar una direcciXn al puntero antes de desreferenciarlo, norma general que tambiZn soluciona el problema de los punteros nulos. /os pasos correctos se ejemplifican en el siguiente cXdigo"

int f )* I char cadFG B %hola%E charQ p B ne8 charF3:GE 99$rimera reserva de memoria dinYmica ....................... delete FG pE 99/iberaciXn de memoria dinYmica p B NU//E 99$recauciXn por posible sobre borrado de #p# ....................... p B cadE 99Nueva asignaciXn Qp B #a#E 990esreferenciaciXn de #p# Nunca se debe desreferenciar un puntero sin antes asignarle un valor. 0atos miembros punteros y copia de objetos /os datos miembro de un objeto pueden ser inicializados mediante un constructor, una inicializaciXn de copia, o asignaciXn de copia. .uponiendo la e7istencia de una clase llamada %-lase7% veamos las siguientes lineas" -lase7 aE -lase7 bE -lase cE -lase7 d B bE 99(nicializaciXn de copia )constructor copia* c B aE 99&signaciXn de copia /a sola declaraciXn de los objetos #b# y #a#, sin parYmetros, invoca un constructor por defecto. !n la tercera l[nea no se invoca al constructor, se realiza una copia del objeto #b# en el objeto #a#. & menos que se especifique algo distinto, esta copia )llamada #asignaciXn de copia#*, produce una replica miembro a miembro de los datos privados de #b# en los datos privados de #a#. & primera vista esto es muy natural y no problemYtico, pero si entre los datos privados figuran punteros entonces pueden plantearse importantes problemas. class -lase7 I int 7E char chE charQ cadE public" -lase7 )int n B 3:* I cad B ne8 char FnGE J 99-onstructor default ]-lase7 )* Idelete FG cadEJ 990estructor .............. J void f)* I -lase7 aE 99(nvoca constructor -lase7 b B aE 99(nicializaciXn de copia , $roblemas con el punteroT -lase7 cE 99(nvoca constructor c B bE 99&signaciXn de copia , $roblemas con el punteroT J

&qu[ hay tres objetos %-lase7% en juego. .e trata de objetos locales, por lo tanto el destructor sera invocado de modo automYtico tres veces al salir de la funciXn. !l primer problema se plantea cuando tomamos conciencia de que, en la funciXn %f ) *%, el constructor es llamado solo dos veces )el destructor" tres veces*, tanto la inicializaciXn de copia como la asignaciXn de copia no utilizan el constructor, sino que copian datos miembro a miembro. !l esquema de los tres objetos seria el siguiente" #a# #b# #c#

0atos privados. a.7 b.7 c.7 Una copia individual a.ch b.ch c.ch por cada objeto a.cad b.cad c.cad ?unciones publicas" -lase7""-lase7 )int*E una copia para todos -lase7""]-lase7)*E los objetos /a copia de los datos #7# y #ch# no presenta ning\n problema, cada objeto tiene su #7# y su #ch# en distintas localidades de memoria, copiar estas variables es copiar el valor almacenado. -ada objeto tiene tambiZn una localidad de memoria para #su# puntero #cad#, el problema es adonde apuntan esos punteros. /a copia de punteros )ej, a.cad B b.cad* es copia de las direcciones a la que apuntan. $ero esas direcciones, en nuestro ejemplo, son localidades de memoria reservadas mediante memoria dinYmica, por lo tanto, luego de" a B bE .ucederY que los punteros %cad% de ambos objetos apuntaran a la misma zona de memoria reservada con ne8, y es aqu[ donde se presenta el problema. 5ay tres objetos y por lo tanto tres punteros, cada puntero deber[a tener sus propios bytes reservados )el default para nuestro ejemplo es 3:*. Una llamada com\n al constructor reserva esos bytes, y son diferentes para cada objeto, pero una asignaciXn de copia hace que un puntero deje de apuntar a #su propia zona# y apunte a los mismos bytes que el otro puntero. -omo consecuencia" ,Una zona de memoria queda fuera de alcance, no pudiendo ser liberada y se crea una fuga de memoria ,0os punteros apuntan a la misma zona, cuando se invoque el destructor, este liberara dos veces una misma zona de memoria, lo que es muy problemYtico. La soluci@n( Cuando entre los datos privados 3ay punteros es necesario e9plicitar un constructor de copia di!erente al de!ault6 para evitar la copia entre punteros6 y es necesario tambiAn proveer de un asi"nador de copia di!erente al de!ault6 esto si"ni!ica -ue para disponer de la notaci@n +a 1 b+ sera necesario sobrecar"ar el operador 515 y darle un sentido di!erente6 -ue evite la copia de punteros( /a naturaleza del problema puede aclararse con un cXdigo que no utiliza clases pero que presenta el mismo error.

void f )* I charQ a B ne8 charF3:GE charQ b B ne8 charF3:GE a B bE 99!rrorT no se debe reasignar sin antes liberar memoria delete FG bE delete FG aE J -ada puntero tiene #sus# 3: bytes de memoria dinamica, al reasignar %aBb% el puntero #a# deja de apuntar a la zona de memoria reservada con #ne8#, esa direccion se pierde y no podra ser liberada )fuga de memoria*. $or otra parte, las dos invocaciones de #delete# cometen el error de liberar una misma zona de memoria.

Você também pode gostar