Você está na página 1de 54

Punteros en C/C++

Punteros en C/C++

Metodologa de la Programacin II

Tema 1 1.3.1 Nociones elementales 1.3.1.1 Que es una variable? Una computadora opera manipulando direcciones de memoria y los valores almacenados en dichas direcciones. Un lenguaje de programacin es una herramienta que permite al programador codificar operaciones binarias en un lenguaje mas cercano a nuestras lenguas naturales. Un programa que realiza la traduccin de instrucciones desde un lenguaje de programacin 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 funcin, este NOMBRE representa para la maquina una localidad de memoria donde el programa puede almacenar y manipular un dato. Una declaracin de variable como: int var; produce una asociacin entre el nombre 'var' y un espacio de almacenamiento en memoria. Por lo tanto hay dos elementos relacionados con el nombre 'var': un valor que se puede almacenar all y una direccin de memoria para la variable, algunos autores se refieren a estos dos aspectos como el "rvalue" y "lvalue" de la variable. Adems del identificador "var", tenemos la palabra "int" que nos indica el TIPO (type) de la variable. El tipo nos indica: 1. CUANTAS CELDAS DE MEMORIA (bytes) se asocian a ese nombre de variable. 2. DE QUE MODO SERAN INTERPRETADOS los datos que se encuentren en tal localidad de memoria. Un byte es la menor unidad de informacin que pueden direccionar la mayora de las computadoras. En la mayora de las arquitecturas el tipo char ocupa un solo byte, por lo tanto es la unidad mnima. Un bool admite solo dos valores diferentes, pero es almacenado como un byte. El tipo integer ocupa generalmente 2 bytes, un long 4, double 8, y as con el resto de los tipos. El otro punto es la relacin entre LO QUE HAY en una celda de memoria y COMO ES INTERPRETADO. Lo que hay en una celda cuya extensin es un byte es simplemente un conjunto de ocho estados posibles (8 bits) que a nivel hardware admiten dos estados diferenciales, estados que pueden ser interpretados como 'verdadero / falso', 0/1, o cualquier otro par de valores. Una celda de memoria del sector de datos, podra contener algo como lo siguiente:

Que es esto? Depende en gran parte del TIPO (type) que hayamos asociado a esa celda (y suponiendo que exista tal asociacin). Ese valor interpretado como un hexadecimal es 0x61, en decimal es 97, y si fue asociada al tipo char representara la letra 'a', cuyo Ascii es igual a 97. En 36 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ 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 har que veamos encendidos ciertos pixeles de pantalla, en los cuales reconoceremos una representacin de la letra 'a'. La representacin binaria de datos ocupa demasiado espacio, por ese motivo es preferible utilizar el sistema hexadecimal, adems de ser muy fcil de traducir a binario es mas econmico que este o el decimal. Observar los bytes de un sector de memoria de un programa facilita la comprensin sobre el modo en que cada tipo (type) se asocia a direcciones de memoria. Supongamos 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.... La representacin de estos datos en memoria, en el segmento de datos, tal como lo muestra un debugger, tendr el siguiente aspecto (se omiten caracteres problemticos para navegadores y dejamos constancia que diferentes compiladores pueden ordenar los datos de otro modo): ffd0 ........................ ffe0 ............6abcd.. fff0 ....................... 20 20 20 20 00 8F 12 00 00 00 F6 FF BC 04 00 FF F6 F6 00 00 F6 FF C7 04 06 36 61 62 63 64 00 00 08 00 00 00 05 00 00 00 0B 01 00 00 00 00 00 00

Los datos que se han declarado primero en el cdigo (int a) figuran al final, los bytes 05 00 son la representacin de la variable entera 'a' de valor = 5, los cuatro bytes 08 00 00 00 lo son del long 'b', luego sigue el array "aaaa", el char '6' que corresponde con el hexadecimal 0x36, y por ultimo un char seteado con el valor entero 6. Adems podemos realizar las siguientes observaciones: 1. Que el segmento de datos almacena los datos comenzando desde el final (0xffff). La primera variable declarada y definida es el entero 'a', que no esta verdaderamente en el final del segmento, es as porque esos valores (como 0B 01) guardan valores de STACK para restablecer algunos registros cuando el programa salga de main() y termine. Sobrescribir ese valor podra producir un crash. 2. Que la variable entera de valor 5 guarda este valor ubicando los bytes al revez. Lo lgico seria que la representacin fuera 00 05, pero los bytes estn invertidos, esto es una norma general de la mayora de los procesadores y responde a una pauta de mayor eficiencia en la lectura de variables numricas. 3. El array 'cad' se declara de modo implcito con 5 bytes, las cuatro letras mas el carcter terminador '\0', se ocupa un byte mas porque un numero par de bytes es mas eficiente. Obsrvese que un array no invierte la posicin de sus elementos.

Metodologa de la Programacin II

37

Tema 1 4. Un char ocupa exactamente un byte. El primer char esta definido con el carcter '6' que corresponde al ascii 0x36, la segunda variable char (hh) es seteada a partir de un valor entero (6) lo que genera una conversin implcita de tipos.

Se podra profundizar mas el tema de que funcin tiene este sector de memoria, su relacin con la pila (STACK) y los modelos de memoria, pero eso se vera en otros apartados. Por ahora es importante tener en cuenta la relacin entre el tipo (type) usado para declarar una variable y el modo en que se almacena en memoria. En la siguiente tabla se encuentran mas ejemplos: DECLARACION int a; char ch; char cad[]="hola"; long a; long a; long a; Inicializacin a = 5; ch = 'e'; a=4 a=0x1234 a = 65535 Representacin Numero en memoria bytes 05 00 65 68 6F 6C 61 00 04 00 00 00 34 12 00 00 ff ff 00 00 2 1 5 4 4 4 de

Cuando 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. La asociacin entre localidades de memoria y variable no siempre existe desde el comienzo al final de un programa. Las variables declaradas como 'locales' a una funcin solo tienen asociada una localidad de memoria mientras el flujo del programa se encuentra en tal funcin, al salir de la misma tales localidades sern usadas por otros datos. En cambio las variables 'globales' o declaradas como 'static' conservan su localidad de memoria durante toda la ejecucin del programa. 1.3.1.2 Que es un array? Un array es una coleccin 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. Estos elementos se encuentran ordenados en celdas consecutivas de memoria. Veamos los siguientes ejemplos: Declaracin e inicializacin int a []= {3, 345, 54, 4}; int a[4]={2}; char a [] = {"Mensaje 1"}; char a [8] = {hola}; long a [] = {9, 16, 0x23b2a}; Representacin en memoria 03 00 63 01 72 01 03 27 02 00 00 00 00 00 00 00 4d 65 6e 73 61 6a 65 20 31 00 68 6F 6C 61 00 00 00 00 09 00 00 00 12 00 00 00 2a 3b 02 00 Bytes 2x4=8 2x4=8 9+1= 10 7+1 = 8 3 x 4 = 12

38

Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ El tipo (type) del array determina cuantos bytes ocupa cada uno de sus elementos, y tambin de que modo se almacena el dato. Es importante mencionar que este modo de inicializacin es solo posible cuando se realiza en la misma lnea que en la declaracin, no es posible inicializar al mismo tiempo varios elementos de un array si lo hacemos en una lnea diferente a la de la declaracin. Tambin hay que mencionar el hecho de que si damos mas elementos inicializadores que los que figuran entre corchetes se genera un error de compilacin, si damos menos elementos el compilador setea el resto de los elementos con el valor '0'. Una cadena en C/C++ es representada internamente como un array de tipo char y utiliza un carcter 'terminador' para indicar el fin de la cadena, ese carcter es el correspondiente al Ascii = 0. Para mas detalles ver cadenas estilo C/C++ . La notacin "Nombre_array[int n]" nos permite seleccionar cada uno de los elementos de ese array, esa expresin tiene el mismo tipo (type) que un elemento individual, y esto puede ser importante para distinguir mas claramente las asignaciones y conversiones posibles. 1.3.1.3 Que es un puntero? Un puntero es un tipo especial de variable, que almacena el valor de una direccin de memoria, esta direccin puede ser la de una variable individual, pero mas frecuentemente ser la de un elemento de un array, una estructura u objeto de una clase. Los punteros, al igual que una variable comn, pertenecen a un tipo (type), se dice que un puntero 'apunta a' ese tipo al que pertenece. Ejemplos: int* pint; //Declara un puntero a entero char* pchar; //Puntero a char fecha* pfecha; //Puntero a objeto de clase 'fecha' Independientemente del tamao (sizeof) del objeto apuntado, el valor almacenado por el puntero ser el de una nica direccin de memoria. En sentido estricto un puntero no puede almacenar la direccin de memoria de 'un array' (completo), sino la de un elemento de un array, y por este motivo no existen diferencias sintcticas entre punteros a elementos individuales y punteros a arrays. La declaracin de un puntero a char y otro a array de char es igual. Al definir variables o arrays hemos visto que el tipo (type) modifica la cantidad de bytes que se usaran para almacenar tales elementos, as un elemento de tipo 'char' utiliza 1 byte, y un entero 2 o 4. 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 expresar con solo 2 bytes (o 4 si es una direccin de otro segmento) Veamos los efectos de un cdigo como el siguiente, en la zona de almacenamiento de datos: char cad[] = "hola"; char * p; p = cad; //Puntero 'p' apunta a 'cad'

Metodologa de la Programacin II

39

Tema 1

El puntero esta en la direccin 0xffee pero el valor que hay en esa localidad de memoria es otra direccin, los bytes "F0 FF" indican que el puntero apunta a FFF0, donde comienza la cadena de caracteres 'cad' con el contenido 'hola' mas el cero de fin de cadena. En las lneas de cdigo no hemos indicado a que carcter del array apunta el puntero, pero esa notacin es equivalente a: p = &cad[0]; que indica de modo mas explicito que se trata de la direccin del primer elemento de ese array de caracteres. El juego con las direcciones puede ilustrarse tambin del siguiente modo: ffee F0 <----- El puntero ocupa dos bytes para representar la direccin FFF0, direccin a la que 'apunta'. ffef FF <----fff0 61 <------ cad[0]. .Primer char del array de caracteres, direccin apuntada por el puntero fff1 61 <------ cad[1] fff2 61 <------ cad[2] fff3 61 <------ cad[3] fff4 0 <------ cad[4] Fin del array, carcter ascii = 0 de fin de cadena Puesto que un puntero tiene como valor una direccin de memoria, es lgico que al llamar a funciones de impresin con un puntero como argumento, la salida en pantalla sea la de una direccin de memoria. Para este tipo de pruebas es interesante usar la librera iostream.h de C++, pues no obliga a especificar el formato (como hace printf ). Para un puntero 'p' la salida en pantalla ser algo similar a lo siguiente: cout<<p; //sale: 0x8f82fff0; printf("%p",p) //sale: FFF0 En este caso se trata de un puntero que almacena en 2 bytes una direccin de memoria, la cual es FFF0. Porque razn la impresin con 'cout' nos da 4 bytes? Porque agrega 2 bytes (8f y 82) para indicar el 'segmento' donde se encuentra esa direccin. Se trata en todo caso de una misma localidad de memoria, con distinto formato de presentacin en pantalla. La 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 direccin de memoria, sino un conjunto de caracteres hasta encontrar el '\0'. Un puntero puede almacenar la direccin de ("apuntar a") muy diferentes entidades: una variable, un objeto, una funcin, un miembro de clase, otro puntero, o un array de cada uno de estos tipos 40 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ de elementos, tambin puede contener un valor que indique que no apunta actualmente a ningn objeto (puntero nulo). Tipos definidos por el programador Tipos como 'bool', 'int' o 'char', son "tipos predefinidos", pertenecientes al lenguaje. En C++ al igual que otros lenguajes, es posible definir tipos nuevos. Las enumeraciones, uniones, estructuras y clases, son tipos nuevos que implementa el programador. La declaracin de un tipo no produce ningn efecto en memoria, no hay ningn identificador donde almacenar un dato, por esa razn no tendra sentido, dentro de la definicin 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; Para asignar un valor necesitamos un objeto, pues un objeto implica una regin de memoria donde almacenar un valor. El almacenamiento en memoria de una unin, enumeracin o estructura (C), no presenta importantes cambios respecto a los tipos predefinidos, sus elementos se ordenaran de modo consecutivo de acuerdo a su 'sizeof'. Respecto a C, C++ aporta un nuevo tipo predefinido, las clases, entidad que no solo es un agregado de datos sino tambin de funciones, y que por ello presenta novedades de importancia respecto a los tipos anteriores. 1.3.1.4 Clases Una clase es bsicamente un agregado de datos y funciones para manipular esos datos. Las clases, y la programacin 'orientada a objetos' en general, ha representado un gran avance para produccin de software a gran escala, los recursos de herencia, encapsulamiento, ocultacin de datos, clases virtuales, etc., estn pensados con esa finalidad. Aqu solo nos detendremos en la nocin mnima de 'clase' y el modo en que es almacenado un objeto en memoria. Supongamos una clase muy simple: class gente { char nombre[10]; int edad; public: gente (char*cad, int a) { strcpy(nombre,cad); edad = a; } }; Se trata de una clase cuyos miembros son dos datos y una sola funcin. Una vez declarada la clase podemos definir objetos como pertenecientes a ese tipo. Una clase no ocupa espacio, pero si Metodologa de la Programacin II 41

Tema 1 un objeto perteneciente a esa clase. El espacio ocupado en memoria por tal objeto puede ser conocido a travs de 'sizeof'. gente pp1; cout<<sizeof(pp1);

//saca en pantalla '12'

El valor podra ser ligeramente diferente segn el compilador, por efecto de optimizacin. Lo 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 funcin. Cada objeto de tipo 'gente' ocupara 12 bytes, pues posee una copia individual de los datos de clase, en cambio hay una sola copia del miembro funcin (aqu el constructor) utilizado por todos los objetos. Declaremos dos objetos de tipo 'gente': gente pp1("gerardo", 33); gente pp2("miguel",34); Observaremos ahora que efectos producen estas entidades 'pp1' y 'pp2', en memoria. Los datos que utilizaremos se obtienen en TurboC++ (cualquier versin) posando el cursor sobre el objeto que nos interesa (aqu 'pp1' y 'pp2') y pulsando 'Alt+f4', tambin consultaremos los registros de la CPU (con "Windows/Registers"). En un programa, que define la clase 'gente' y dos objetos (pp1 y pp2) inicializados como muestran las lneas de cdigo previas, se puede observar lo siguiente:

El valor especifico de cada dato (como el valor de segmento) puede variar con cada ejecucin, lo que cuenta es la relacin entre tales valores. Interpretemos estos datos. 1. En la ventana de cada objeto (pp1 y pp2) figura en primer lugar la direccin de memoria donde almacena sus valores, ambas direcciones tienen el mismo valor de segmento (0x8F86), que coincide por otra parte con el valor de DS (segmento de datos) y de SS (segmento de stack) de la CPU. Sus direcciones difieren ligeramente en offset, la resta de los mismos (0xFFEA - 0xFFDE) es igual a 12, que es el espacio que ocupa (en bytes) cada objeto en memoria. 2. Esos 12 bytes por objeto corresponden a 10 para la cadena de caracteres ('cad') y 2 para almacenar el entero ('edad'). Estos datos estn almacenados all donde indica el offset, no en otro sitio, por lo tanto un puntero al objeto 'pp1' apuntara (en este caso) a la misma direccin de memoria que un puntero a su elemento 'cad', y otro tanto para 'pp2'. Las datos miembros se exponen con sus nombres a la izquierda y el valor que contienen a la

42

Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ derecha. La cadena de caracteres es terminada en '\0' (seguido de caracteres aleatorios), y el entero es mostrado en formato decimal y hexadecimal 3. Debajo, y separado por una lnea, se encuentra un espacio donde se enumeran las funciones miembro de la clase. All encontramos el prototipo de la funcin miembro y al lado la direccin de memoria donde se inicia su cdigo. Ese es el valor que almacenara un puntero a dicha funcin. Obsrvese que tal direccin es la misma para ambos objetos, por la razn antes mencionada de que hay solo una copia de funciones miembro por objeto. El segmento donde se encuentra tal funcin se corresponde con el valor que muestra la ventana CPU para CS (segmento de cdigo). Podemos 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 ningn valor. Un objeto de tal clase si define una regin de memoria, un espacio de almacenamiento de datos. Esta es la diferencia entre 'clase' y 'objeto'. Cada 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.

Por otra parte, un array de objetos (instancias de clase) es almacenado como una sucesin consecutiva, mientras que un puntero a objeto ser (como todo puntero) un par de bytes que apunte a una direccin de memoria donde se almacena el objeto. 1.3.2 Declaracin e inicializacin Un puntero, como cualquier variable u objeto, adems de ser declarado (para comenzar a existir) necesita ser inicializado (darle un valor de modo controlado), lo cual se realiza mediante el operador de asignacin ('='). Desde 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. 1.3.2.1 Assignacin errnea : "Cannot assign..." En primer lugar veremos un caso simple de asignacin errnea para comprender el mensaje enviado por el compilador. void main(){ int a; int* b; b = a; //Error } El programa no compila, y recibimos el mensaje de error: "Cannnot assign 'int' to 'int near*' en function main(); Hemos tratado de inicializar el puntero 'b' asignndole un valor equivocado, de otro tipo. El anlisis de los mensajes de error siempre es instructivo, profundicemos en este. Metodologa de la Programacin II 43

Tema 1 En primer lugar: Que es un "int near*" ?. Los punteros pueden ser clasificados como 'near' (cercano) o 'far' (lejano) de acuerdo a si la direccin apuntada se encuentra en el mismo segmento que el puntero. Las principales diferencias se exponen en el siguiente cuadro. Tipo puntero near far de Caractersticas Cantidad de bytes que utiliza el puntero

La direccin apuntada se encuentra en el mismo segmento que el dos - (offset) puntero Se encuentran en diferente segmento cuatro - (segmento::offset)

Si un programa no requiere de una gran cantidad de datos significa que pueden entrar en un solo segmento, y los punteros sern 'near' por defecto, en caso contrario el default ser 'far'. Esto es determinado directamente por el modelo de memoria utilizado por nuestro programa. En segundo lugar, el mensaje nos indica una discordancia de tipos. Uno es 'int', y el otro es 'int near*', si obviamos la caracterstica de 'near' vemos que la expresin "int*" coincide con nuestra declaracin del puntero. Lo mas instructivo de esto es comprender que el asterisco pertenece al tipo (type), no al nombre ('b'). Algunos autores discuten sobre cual de las dos siguientes declaraciones es la mas adecuada para declarar un puntero: int *b; int* b; Ambas son perfectamente validas, la nica diferencia es que el primer caso se sugiere que '*' forma parte de 'b', y en el segundo que '*' forma parte del tipo. Lo recomendable es adoptar la segunda forma, la primera se presta a confundir el operador '*' con el operador de 'indirection', y es muy importante comprender que aqu no hay nada de 'indireciton', es solo una declaracin de un identificador (b), ligado a un tipo (int*). Es el mensaje del compilador el que nos indica esta ultima interpretacin. Para que el programa compile sin problemas es necesario utilizar el operador '&' antes del nombre de la variable, el efecto del operador es devolver la direccin en memoria de la variable, la cual se asigna naturalmente a un puntero. void main(){ int a; int* b; b = &a; //El puntero 'b' apunta a 'a'. } Una variable individual de tipo 'T' y un array de elementos de tipo 'T' pertenecen a tipos diferentes, no es posible la asignacin entre un entero y un array de enteros. Intentemos sin embargo tal asignacin:

44

Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ void main(){ int a; int b[4]; a = b; //Asignacin errnea, no compila } Lo mas interesante del ejemplo es que el mensaje de error es similar (pero inverso) al de nuestro primer ejemplo fallido: "Cannnot assign 'int near*' to 'int' en function main(); Lo cual puede resultar sorprendente, pues en el ejemplo no hemos declarado ningn puntero, solo un entero y un array de enteros. Lo que esta sucediendo es que el compilador se esta refiriendo al array de enteros como un "int near*", como un puntero. Un array y un puntero no son exactamente lo mismo, hay algunas diferencias, pero la relacin que existe entre ambos es muy estrecha y la sintaxis aplicable a ambas entidades es en gran parte idntica. Esta relacin explica que el siguiente ejemplo compile bien sin ninguna complicacin: void main(){ int a [4]; int* b; b = a; //o bien --> b = &a[0]; } Podra haberse esperado algn problema, puesto que no hemos obtenido la direccin del array con el operador '&', pero no ocurre as, el solo nombre del array es tomado como sinnimo de la direccin de su primer elemento (o puntero a su primer elemento). Bien, esta ha sido una introduccin para comprender el mensaje de error tpico en una inicializacin fallida. El intento de asignar una variable individual a un array produce un mensaje de error distinto ("Lvalue requerido"). Veamos ahora ejemplos de inicializaciones de punteros correctas. 1.3.2.2 Opciones de inicializacin Un puntero puede ser inicializado con la direccin de memoria de un objeto, tal objeto debe pertenecer a un tipo acorde al tipo al que apunta el puntero. Puede tratarse de la direccin de un elemento de un array o de una variable individual, el operador '&' antepuesto a un objeto nos devuelve su direccin de memoria. Tambin puede utilizarse un "literal", ya sea numrico, de carcter, o de otro tipo, y puede inicializarse como puntero nulo, en este caso esta permitido usar el 0, el nico entero permitido, por su equivalencia con el valor NULL. Suponiendo un tipo cualquiera "T", son inicializaciones validas las siguientes:

Metodologa de la Programacin II

45

Tema 1 Puntero inicializado partir de: Un objeto individual T x; Un array de objetos T x [10]; Otro mismo T* x; Valor 0 Null=0 puntero a Declaracin e inicializacin Declaracin e inicializacin en una misma lnea desdobladas T* ptr; T* ptr = &x; ptr = &x; T* ptr; T* ptr = &x[0]; ptr = &x[0]; T* ptr; T* ptr = x; ptr = x; del T* ptr; tipo T* ptr = x; ptr = x; nulo T* ptr = 0; T* ptr = NULL; T* ptr = [literal] T* ptr; ptr = 0; T* ptr; ptr = NULL; T* ptr; ptr = [literal];

puntero

Un literal

Sobre este cuadro caben las siguientes aclaraciones: 1. Inicializar 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. 2. La equivalencia entre el valor 0 (cero) y NULL es de uso general, sin embargo existen compiladores que dan a NULL un valor diferente a cero. 3. Un 'literal' debe ser el apropiado para el tipo de puntero inicializado. Si es un puntero a char, una cadena de caracteres cualquiera (ej: "hola") ser un literal adecuado, si se trata de tipo numrico, para un int "4" Ser apropiado. Si tomamos como ejemplo el tipo "char", siguiendo al cuadro anterior, tenemos las siguientes opciones de inicializacin: Puntero inicializado a partir Declaracin e inicializacin en de: una misma lnea Un elemento char* p = &ch; char ch; Un array char cad[10]; char* p = cad; char* p = &cad[0]; Declaracin e inicializacin desdobladas char* p; p = &ch; char* p; p = cad; char* p; p = &cad[0]; char* p; p = 0; char* p; p = NULL;

Valor 0 = puntero nulo char* p = 0; Null=0 (0 es el nico valor entero que puede char* p = NULL; inicializar un puntero) 46 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Otro puntero (ya inicializado) char* p = ptr; char *ptr; Un literal de cadena char* p = "casa"; "casa"; char* p; p = ptr; char* p; p = "casa";

Se ha insistido lo suficiente en que un puntero almacena como valor una direccin de memoria, por eso la presencia de un 'literal', en esta ultima tabla el literal de cadena "casa", puede sorprender. Es 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 direccin (por medios normales) pero existe, 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 funcin pueda retornar una constante sin temor a que dicho almacenamiento se pierda al salir de la funcin, un literal no es una variable 'local'. No hay obstculos para inicializar un puntero con una variable constante, por lo tanto lo mismo se aplica a literales de cualquier tipo. Las tablas anteriores no abarcan todos los casos posibles de inicializacin de punteros, aun no se han mencionado los casos donde el puntero apunta a una funcin o un objeto miembro de una clase, ni la opcin de inicializar a travs de memoria dinmica. 1.3.2.3 Inicializacin a travs de memoria dinmica Esta modalidad se diferencia de todas las enumeradas hasta ahora y puede considerarse como la principal. Todas las formas vistas hasta aqu asignan al puntero la direccin de memoria de otra entidad (elemento, array, puntero, literal) adems del caso especial del valor NULL. Ya se ha mencionado que la declaracin de un puntero no implica la reserva de memoria, salvo 2 bytes para almacenar una direccin, por esa razn podra decirse que el puntero, cuando es inicializado por otro elemento, 'vive' de la memoria que le aporta el objeto al que apunta. La reserva de memoria dinmica requiere el uso obligado de un puntero, el cual apuntara al comienzo de la zona reservada. Lo diferente aqu es que se trata del nico caso donde el puntero no necesita de otro elemento que le aporte memoria necesaria, no necesita apuntar a algn otro objeto. Cuando reservamos dinmicamente 40 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 existe. A pesar de no existir propiamente un segundo objeto, sigue siendo esencial, el tipo (type) segn el cual se declara el puntero, pues esto determina el modo en que el puntero nos permitir acceder a tal zona de memoria. No hay un 'objeto apuntado', pero el puntero se conduce igual que si lo hubiera, por esa razn hablaremos en general del 'objeto apuntado' por el puntero, sin aclarar el caso especial que estamos considerando. En C++ la reserva y liberacin de memoria dinmica se realiza a travs de los operadores new y delete, y su sintaxis, para un puntero de nombre 'ptr' es la siguiente:

Metodologa de la Programacin II

47

Tema 1 Reserva liberacin

Elemento individual T* ptr = new T; delete ptr; de tipo 'T' Array de 'n' elementos T* ptr = new T[n]; delete [] ptr; de tipo 'T' A travs del operador new solicitamos una cierta cantidad de memoria dinmica, es posible que no exista suficiente memoria disponible, en tal caso el operador nos devolver un puntero NULL (o apuntando a 0), y es por esta razn que luego de una solicitud es recomendable inspeccionar si el puntero devuelto es nulo. Esta seria la respuesta 'clsica' a una reserva fallida de memoria dinmica, sin embargo existen diferentes compiladores que, ajustndose al standard C++ no devuelven un puntero nulo sino que lanzan una excepcin (bad_alloc). Algunos viejos compiladores no reconocen la opcin de borrar el puntero con corchetes vacos y nos exigen que especifiquemos el numero de bytes a borrar (los mismos que los reservados), TC++ a partir de su versin 3.0 admite esa notacin. Escribir "delete ptr;", sin los corchetes, solo libera el primer elemento del array, y es por lo tanto un error importante. 1.3.2.4 Desreferenciacion ("indirection") Un puntero almacena una direccin de memoria de alguna entidad, esto en si mismo no seria demasiado til si no fuera posible, a travs del puntero, acceder a lo que esta almacenado en esa direccin. segn el creador de C++: "La operacin fundamental de un puntero es desreferenciar, es decir, referir al objeto al que apunta el puntero." (Stroustrup, 1997). A continuacin desarrollaremos esta definicin. La funcin de 'desreferenciar' un puntero es llevada a cabo por el operador '*', que adems cumple otras funciones en C++. Como su papel es complementario a una de las funciones del operador '&' se comenzara estudiando la relacin y diferencia de estos dos operadores Ambos operadores tienen mas de un sentido dependiendo del contexto en que aparecen, por lo tanto son casos de sobrecarga de operadores. Veamos sus distintos usos: OPERADOR * Multiplicacin int a = 3, b=2,c; c = a * b; declaracin type puntero int n; int* p = n; Dereferencing (indirection) cout<<*p; & Bitwise

Usos

Operation AND char a=0x37; a &=0x0F; declaracin del type referencia int a; int &b = a; Referencing cout<<&a;

El primer uso de cada operador se distingue claramente de los otros dos, derivan de C y no tienen relacin con el tema punteros. Los que figuran en segundo lugar pertenecen a la sintaxis bsica de declaracin de punteros y referencias. Nos concentraremos en el tercer significado de estos operadores. 48 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ El papel opuesto y complementario del tercer uso de ambos operadores se podra sintetizar as: dadas las siguientes declaraciones: int v = 4; int* p = &v; El puntero 'p' es equivalente a la direccin de memoria a la que apunta. cout<<p saca en pantalla una direccin de memoria (por ej: 0x8f70fff0) Mientras que la expresin '*p' es sinnimo del elemento individual que se encuentra en la localidad apuntada por el puntero cout<<*p saca en pantalla '4'

La variable 'v' es equivalente al valor que almacena cout<<v saca en pantalla '4' Mientras que la expresin '&v' es un sinnimo de la direccin de memoria donde se encuentra esa variable cout<<&v saca en pantalla una direccin de memoria (ej: 0x8f70fff0)

Como 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 '*'), en el otro ('&') dada una variable accedemos a la direccin de memoria donde almacena su valor. El termino usado para este efecto del operador '*' es el de 'indirection' o 'dereferencing' traducido generalmente como 'in direccin' o 'desreferenciacion'. Su sentido mas llano seria: operador que permite referirnos al elemento individual apuntado por el puntero, en lugar de la direccin en que ese elemento se encuentra almacenado. A 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 lnea "cout<<p" no hubiera sacado en pantalla una direccin de memoria, sino una cadena de caracteres. El caso especifico de un puntero a char Dadas las siguientes declaraciones e inicializaciones: char cad[] = "hola"; char* ptr = cad; //aqu el '*' es un indicador de tipo, no de 'indirection' El puntero 'ptr' apunta a 'cad', al char inicial de 'cad'. Veamos ahora que saldra en pantalla con 'p' y con '*p', para el caso volveremos a usar la librera "iostream.h" de C++, pues las funciones de C impondran su formato a la salida. cout<<ptr; cout<<*ptr; //sale en pantalla: "hola" //sale en pantalla: 'h'

Lo que puede desorientar aqu es que 'ptr' no imprima en pantalla una direccin de memoria, que es lo esperable tratndose de un puntero. Se trata de una caracterstica propia de las funciones que tratan con punteros a char, y no de un rasgo diferencial de los punteros a char, estos tienen las mismas caractersticas generales de cualquier puntero. Utilizando una funcin C de "stdio.h", las lneas anteriores son equivalentes a Metodologa de la Programacin II 49

Tema 1 printf("%s", ptr); printf("%c", *ptr); Analicemos el funcionamiento de printf. Esta funcin, recibe como argumento un puntero a char, algo cuyo tipo es char*, es decir una direccin de memoria. En C o C++ no hay otro modo de pasar un array a una funcin que a travs de una direccin de memoria. El especificador de formato, "%s", le indica a la funcin que interprete esa direccin como siendo el comienzo de una cadena de caracteres (la 's' es de 'string'). Lo que la funcin hace es interpretar los bytes, uno a uno, como indicando caracteres ascii, y los sacara ordenadamente en pantalla hasta encontrar un '\0', sin importar donde se encuentre ese '\0' o si excede o no la capacidad del array original. En C++, el flujo de salida 'cout' y el operador de insercin '<<', no requieren de un formato especifico para sacar algo en pantalla, esto significa que imponen un formato predeterminado segn el dato enviado como parmetro. En el caso de que este parmetro sea un puntero a char imponen el formato "cadena de caracteres", exactamente igual que printf con formato "%s". Esto no es obvio, dado que se trata de un puntero podran sacarlo en pantalla como una direccin de memoria, pero no ocurre as. Es por esta razn que la idea de 'indirection' se oscurece en relacin a 'punteros a char', pues las funciones standard de impresin en pantalla de C y C++ no tratan a tal puntero como una direccin de memoria mas (aunque lo sea). Siendo 'p' un puntero a tipo char, para las funciones standard de impresin: 'p' es la cadena apuntada, '*p' el carcter individual apuntado. 1.3.2.5 Asignacin de punteros Un puntero puede ser asignado a otro puntero del mismo tipo a travs del operador '='. El significado de tal asignacin es similar al de una asignacin entre variables, el valor almacenado en el elemento de la derecha se copia en el elemento de la izquierda. Solo que en el caso de punteros este valor es una direccin de memoria, y esto puede producir un efecto distinto al esperado. void f (char* cad1, char* cad2) { cad1 = cad2; *cad1 = '3'; //Efecto: modificacin de cadena "dos". //................................. } char uno = "1111"; char dos = "2222"; f(uno, dos); //Llamado a funcin f(); La funcin 'f()' recibe dos punteros a char desde otra funcin, sigue luego una asignacin de 'cad2' en 'cad1', y una modificacin de un char a travs de desreferenciacion del puntero. Si la intencin era copiar el contenido de la cadena original "dos" en la cadena "uno", para modificar "uno" sin alterar "dos", estamos ante un error. El carcter '3' se copiara en la cadena original "dos", por la razn de que luego de la asignacin de punteros (cad1=cad2) ambos apuntan a "dos". 50 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Hay casos donde puede ser til que dos punteros apunten a una misma direccin, y entonces Serra correcto asignar punteros mediante el operador '=', 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. Dados dos punteros ("pt1" y "pt2") a array, que apuntan a direcciones diferentes (son dos arrays diferentes), los efectos de una asignacin de punteros y copia de array son los siguientes: Operacin pt1 = pt2; while (*pt2!=0) *pt1=pt2; Efecto asignacin de punteros. Lo que se copia realmente son los 2 (o 4) bytes de direccin de memoria. El puntero 'pt1' deja de apuntar a al array original, ahora apunta a la misma direccin que 'pt2'. Copia de array. La copia se realiza elemento por elemento. Se copian tantos elementos como caracteres tenga el array 'pt2'. En el caso de una cadena de caracteres, podemos confiar en el '\0' para saber cuantos elementos copiar.

Es muy importante diferenciar ambas operaciones. Un array no puede ser copiado mediante el operador de asignacin '=', hay que copiar elemento por elemento, un puntero puede ser copiado con tal operador, pero el efecto provocado puede ser distinto al efecto deseado. La confusin entre copia de punteros y copia de array puede provocar otro tipo de problemas en relacin a memoria dinmica o constructores de copia, problemas que se analizan mas adelante. 1.3.3 Punteros a 'void' Un puntero puede apuntar a un objeto de cualquier tipo, predefinido por el lenguaje o definido por el usuario. 'Void' es el nombre de un tipo, pero su uso esta sujeto a mayores restricciones respecto a otros tipos. El termino 'void' puede ser usado como tipo de una funcin o de un puntero, pero no para declarar un objeto. Las declaraciones posibles de tipo void, en C y C++, se resumen en el siguiente cuadro. Declaraciones Objeto de tipo void Ej: void x; Retorno de un funcin Ej: void func(){....... Puntero a Ej: void* p; C C++ No permitido. Mensaje de error : "Objeto de sizeof desconocido." El compilador no esta en condiciones de determinar el monto de memoria que requiere el objeto. Significa que la funcin no retorna ningn valor ('tipo pseudodevuelto'). Un puntero a void es tratado Puntero a objeto de tipo desconocido. void como un puntero a char. Requiere conversin explicita a otro tipo antes de ser utilizado. Conversin implcita.

En C se accede a bytes no vinculados a ningn tipo mediante punteros a char, esto es natural si se considera que un 'char' ocupa 1 byte de almacenamiento, y de may que exista conversin implcita entre ambos tipos. Metodologa de la Programacin II 51

Tema 1 La funcin C standard "memset()", retorna un puntero a void. El siguiente cdigo es aceptable en C: void f(char*cad, char ch, int n){ char* s; s = memset(cad,ch,n); //conversin implcita de void* a char* } //valido en C, pero no en C++. C++ no lo permite debido a su mas estricta comprobacin de tipos. Lanza el mensaje de error, "no se puede convertir void* a char*". Hay otras funciones similares a memset que derivan de C y retornan void, algunas declaradas en "mem.h" y "string.h". Todas pueden ser utilizadas igualmente en C++, pues la cadena afectada por la funcin es enviada como parmetro. Si se quisiera utilizar el puntero de retorno seria necesario un puntero a "void" o una conversin explicita, por ejemplo: s = (char*)memset(cad,ch,n); //valido en C++

El significado en C++ de un puntero a 'void' es el de un puntero que 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 cdigo que trabaja a nivel hardware o relacionado con administracin de memoria. Las operaciones permitidas y no permitidas, en C++, para un puntero a void se resumen en el siguiente cuadro: 1- Asignar a void* un puntero de cualquier tipo Operaciones 2- Asignar un void a otro void permitidas 3- Convertir explcitamente un void a otro tipo 4-Comparaciones de igualdad o desigualdad entre void* 1-Usar un void Operaciones 2-Convertir implcitamente un void a otro tipo (no void) prohibidas 3-Desreferenciar un void. 4-Asignar a void punteros a funciones o a miembros 1.3.3.1 Punteros y 'const' Un puntero implica la intervencin de dos elementos: el puntero y el objeto apuntado (salvo que sea nulo). El termino reservado "const" puede tener dos significados diferentes segn el sitio que ocupe en la declaracin, haciendo constante al puntero o al objeto apuntado. 1.3.3.2 Puntero constante El operador '*const', en lugar de '*' solo, declara al puntero como constante, esto significa que la direccin a la que apunta el puntero no puede cambiar en todo el programa. La variable apuntada si puede cambiar. Ejemplo: 52 Dpl. Ing. Carlos Balderrama Vzquez void*=T* void* = void* T* = (T*)void* (void*!=void*) (void*)++; T* = void* *v;

Punteros en C/C++ int a = 5; int *const ptr = &a; //Puntero constante a int *ptr = 4; //Bien, se modifica la variable ptr = NULL; //Error, intento de modificar el puntero constante Al no poder modificar la direccin a la que apuntan, estos punteros se aproximan al sentido que tiene una referencia 1.3.3.3 Puntero a constante aqu el termino "const" afecta al tipo al que apunta el puntero. int a = 5; const int* ptr = &a; ptr = NULL; //Bien, el puntero puede cambiar, ser reasignado *ptr = 6; //Error. No se puede cambiar el objeto apuntado. Es importante observar que en el ejemplo la variable 'a' no fue declarada originalmente como "const", pero el puntero la toma como "constante". En este sentido, aunque la variable no puede ser modificada a travs de ese puntero, si podra serlo a travs de otro identificador, el propio nombre de la variable u otro puntero que no apunte a const. Declarar un puntero a const suele ser til al declarar argumentos de funciones, sirve para especificar que el argumento puntero no puede ser modificado dentro de la funcin. La funcin C standars "strcpy", en su declaracin: char* strcpy (char*p, const char* q); impide que el segundo argumento sea modificado por la funcin. La funcin copia el contenido de 'q' en 'p', por esa razn el primer argumento, que no es 'const', Serra modificado. Esto no significa que al llamar a la funcin el segundo parmetro necesite ser una constante, es 'tomado' como constante por el puntero de la funcin. El siguiente cuadro resume la sintaxis de punteros y 'const': Entidad Puntero constante Comentario Puntero constante, no puede modificar la direccin a la int *const ptr = &a; que apunta. const int* ptr = &a; Puntero que apunta a const. No puede modificarse el Puntero a const objeto apuntado a travs de ese puntero. int const* ptr = &a; notacin alternativa para puntero a const. Puntero const const int *const ptr = No puede modificarse la direccin apuntada ni el objeto a const &a; a travs de ese puntero. Como ya hemos visto, se puede asignar una variable no constante a un puntero a constante, esto por la razn de que no puede producir ningn perjuicio, un puntero a const es un puntero con Metodologa de la Programacin II 53 Ejemplo

Tema 1 restricciones. Lo inverso, asignar una variable constante a un puntero que no apunte a const, no esta permitido, pues se perdera el sentido de haber restringido la operatividad de la variable y existira el peligro de modificar sus datos. 1.3.3.4 Puntero nulo ("Null pointer") Algunos autores definen a este puntero como "aquel que no apunta a ningn sitio" y otros como "un puntero que no apunta a ningn objeto" (Stourtrup-1997). La segunda definicin es mas clara, mientras que la primera puede introducir alguna confusin. De hecho no esta claro que podra significar que 'no apuntar a ningn sitio'. El concepto de 'puntero nulo' existe por la necesidad practica de hablar de un puntero que no esta ligado a ningn objeto, muchsimas funciones de las libreras de c/c++ devuelven punteros y entre los posibles valores de retorno cuentan con el de puntero nulo (ej: strchr () ), tambin se presenta cuando solicitamos memoria dinmica, el operador 'new' retorna un puntero nulo si no hay memoria suficiente (no todos los compiladores). La localidad de memoria donde esta el puntero contiene siempre algn valor (!no existen celdas vacas! cero es un valor!). Un puntero apunta a una direccin, la indicada por el valor que almacena, por lo tanto es lgico concluir que un puntero siempre apunta a algn sitio. Lo que distingue a un puntero nulo no es que 'no apunte a ningn sitio' sino que apunta a alguna localidad de memoria que, por convencin del compilador utilizado, no puede estar asociada a ningn objeto o variable. Ese valor, esa direccin 'prohibida' para almacenar all algn objeto, varia para diferentes compiladores, para Borland (y la mayora) es la direccin 0 (cero) del segmento de datos. Cuando el puntero apunta a la localidad 0 el compilador considera que su valor es 'null', o lo que es lo mismo, para este compilador 'null' es equivalente a cero. Esa direccin existe y es el comienzo del segmento de datos-stack, puede ser visualizado, y si no hay errores su valor debera ser 0. Existe otro concepto que no debe confundirse con el de puntero nulo, el de "wild pointer". Un puntero nulo apunta a un sitio bien determinado, en cambio un 'wild pointer' puede estar apuntando a cualquier sitio, una direccin indeterminada dentro del segmento. 1.3.3.5 Puntero a puntero Un puntero almacena la direccin de un objeto, puesto que ese objeto puede ser otro puntero, es posible declarar un puntero que apunta a puntero. La notacin de puntero a puntero requiere de un doble asterisco, '**', la sola notacin suele generar un efecto de confusin considerable, y es la razn de que Mats Henricson y Erik 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 Rec 48. Sin embargo, como se vera, el concepto en si mismo no es complejo. La relacin entre una variable comn, un puntero y un puntero a puntero se muestra en las siguientes lneas: int a = 4; int* pt1 = &a; int**pt2 = &pt1; 54 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Por un lado tenemos el valor que almacena la variable 'a', el puntero 'pt1' almacena la direccin de esa variable, y el puntero 'pt2' almacena la direccin del puntero 'pt1'. Son 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. declaracin e inicializacin int a = 4; int* pt1 = &a; int**pt2 = &pt1; direccin de memoria (hipottica) 0xfff6 0xfff4 0xfff2 Valor que almacena en tal direccin de memoria 4 0xfff6 0xfff4

Es interesante comprobar las diferentes salidas en pantalla de 'pt2' en los siguientes casos: cout<<pt2; //Imprime la direccin del propio puntero 'pt2', aqu: "0xfff2" cout<<*pt2; //Imprime la direccin almacenada en 'pt2', "0xfff4" cout<<**pt2; //Imprime el valor almacenado en '*pt1 = a', "4". El comportamiento de la salida en pantalla es coherente, pues se cumplen las siguientes igualdades: *pt2 == pt1; //Desreferenciacion de 'pt2' *(*pt2) == *(pt1); //Aplicamos '*' a ambos lados *pt1 == a; //De esto y la lnea previa se deduce... **pt2 == a; //...esta igualdad Lanse las anteriores lneas como 'igualdades' (comparaciones que dan 'Verdadero') y no como asignaciones. La estrecha relacin existente entre los conceptos de puntero y array, es la razn de que el asterisco doble (**) pueda ser interpretado indistintamente como puntero a puntero, o bien como un array de punteros. 1.3.4 Puntero a funcin Es posible tomar la direccin de una funcin y asignarla a un puntero. Una funcin tiene una direccin, esta se encuentra dentro del segmento de cdigo y marca el comienzo del cdigo para esa funcin. Un puntero a funcin se declara especificando el tipo devuelto y los argumentos aceptados por la funcin a la que apunta, estos dos elementos del puntero deben coincidir con los de la funcin. La sintaxis para estos punteros es la siguiente: FUNCION Ejemplo: PUNTERO A FUNCION Ejemplo: Inicializacin de puntero a funcin Type devuelto Nombre Int f1 Type devuelto (*Nombre) int (*pf) pf = &f1; (Las dos formas estn pf = f1; bien) (argumentos) (int,char*); (argumentos) (int, char*);

Metodologa de la Programacin II

55

Tema 1 La sintaxis de una expresin como: int (*pf) (int, char*); puede resultar poco obvia, a veces es cmodo definir un tipo (con typedef) para simplificar las declaraciones. Tambin puede declararse un array de punteros a funcin (todas deben coincidir en tipo devuelto y parmetros), un ejemplo de ambos recursos se ve a continuacin: typedef void (*pmenu) (); pmenu Archivo [] = {&Abrir, &Guardar, &Cerrar, &Salir}; En primer lugar definimos un tipo, que es un puntero a funciones. Ese tipo nos permite inicializar otros punteros a funciones, en este caso un array de punteros a funciones. Invocar un puntero a funcin no requiere de desreferenciacion y es muy similar a un llamado comn de funcin. Su sintaxis es: Nombre_de_funcion (argumentos); //Puntero a funcin Nombre_de_funcion [indice] (argumentos); //Para un array de punteros a funcin Un puntero a funcin solo puede ser inicializado utilizando la direccin de una funcin, debe existir concordancia entre funcin y puntero respecto a tipo devuelto y argumentos. Se trata de un puntero especial, no requiere almacenamiento extra de memoria y no esta hecho para itinerar ni para aritmtica de punteros, solo para almacenar la direccin de una funcin, por medio de la cual esta es invocada. 1.3.4.1 Punteros a objetos Pueden declararse punteros que apuntan a objetos instancias-de-clase de cierto tipo, la sintaxis a utilizar para la declaracin es la comn, solo es diferente el modo en que es invocado un dato o funcin miembro. Con tal fin se utiliza el operador "->". Al igual que punteros que apuntan a tipos definidos en el lenguaje, un puntero a objeto, luego de declarado, puede ser inicializado con la direccin de memoria de un objeto conveniente (del mismo tipo), sea de un objeto individual o un array, tambin se lo puede inicializar como puntero nulo o a travs de otro puntero ya inicializado. Sin embargo estas opciones hacen al puntero una entidad dependiente de la memoria que se haya reservado para los objetos que asignan su direccin al puntero. Para que el puntero tenga respaldo independiente en memoria la va indicada es la reserva de memoria dinmica para el mismo. Si existiera una clase llamada Fecha, una declaracin de puntero a objeto y la reserva de memoria dinmica tendra la forma: Fecha* hoy; hoy = new Fecha; O bien: 56 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Fecha* hoy = new Fecha; La cantidad de memoria a reservar Serra calculada de modo automtico a travs del sizeof de la clase. Ahora bien, el sizeof de una clase no es un valor dinmico, es un valor fijo que queda establecido en tiempo de compilacin y no se modifica, de modo que si miembros de la clase necesitaran memoria dinmica esto debera implementarse de algn modo en otro sitio, especficamente a travs de un constructor. 1.3.4.2 El puntero implcito "this" Cuando una funcin miembro (mtodo) es invocada para un objeto, la funcin siempre recibe un parmetro extra, no declarado, que es un puntero al objeto que invoco la funcin, ese puntero recibe el nombre genrico de "this", y puede ser usado de modo implcito o explicito. Veamos esto detenidamente. Una clase puede contener miembros 'privados', 'pblicos' o 'protegidos', para este tema solo consideraremos los dos primeros. Se trata de especificadores de acceso. Uno de los primeros pasos al manipular clases es la comprobacin de que un dato declarado como 'private' no puede ser accedido de modo normal en el cuerpo de cualquier funcin. Para poder acceder a los datos privados debemos utilizar funciones miembros de la clase donde estn declarados. Supongamos la siguiente clase: class num{ int x; public: par (){x=0;} void set () {x=3;} void set2 (int a) {x=a;} void set3 (int x) {this->x=x;} .............. }; Declara varias funciones miembro, se han definido dentro de la clase solo para simplificar la exposicin. La primera se distingue por ser un constructor, pero todas tienen en comn el acceder al dato privado 'x' para darle un valor. No es inmediatamente obvio como es posible para una funcin miembro acceder al dato privado 'x', recordemos el modo en que una funcin cualquiera accede generalmente a datos. Una funcin comn (no miembro) puede acceder a datos: Globales: declarados fuera de toda funcin. Locales: declarados dentro de esa funcin. parmetros: enviados por otra funcin y que son utilizados directamente (por referencia) o a travs de una copia local (por valor)

Metodologa de la Programacin II

57

Tema 1 En apariencia el dato 'x' no pertenece a ninguna de estas categoras. Cuando 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. Es decir, luego de: num uno; num dos; Existen dos objetos y cada uno tiene su propia 'x'. Esa es la 'x' que aparece en las funciones definidas en esta clase, Serra una variable distinta para cada objeto que invoque las funciones (mtodos). La cuestin es: como llega ese valor a la funcin, para que esta puede operar con el mismo? La respuesta es: llega de modo implcito a travs de un puntero a ese objeto, el puntero "this". Tomando como ejemplo la clase definida antes, es como si sus funciones miembro hubieran sido declaradas y definidas de este modo: void set2 (num* this, int a) { this->x = a; } Solo que el puntero 'this' esta implcito, no es necesario mencionarlo en la lista de parmetros y la mayora de las veces no es necesario mencionarlo en el cuerpo de la funcin tampoco. Se puede acceder a la variable 'x' no porque sea 'global' ni 'local', sino porque llega como parmetro, solo que es un parmetro especial, implcito. Vamos a mencionar dos casos, frecuentes, donde es necesario explicitar el puntero "this": Existe ambigedad respecto a los nombres de variables. Una funcin miembro retorna una referencia al objeto que la invoco.

El primer caso se produce si un parmetro tiene el mismo nombre que un dato privado, en tal caso la ambigedad se resuelve utilizando el nombre del parmetro, y para poder acceder al dato privado Serra necesario explicitar el puntero 'this'. Es lo que sucede en el siguiente caso: void set3 (int x) {this->x=x;} Si el parmetro tuviera otro nombre ya no seria necesario (aunque tampoco seria un error) explicitar el 'this'. Puede parecer una complicacin innecesaria dar al parmetro el mismo nombre que un dato privado, pero se trata de un recurso a veces til para detectar rpidamente, en una funcin de seteo, la relacin entre parmetros y datos privados. El segundo caso se produce al retornar una referencia al objeto que invoca la funcin, se trata de un recurso frecuente en la sobrecarga de operadores, pues permite concatenar operaciones, al modo de los operadores '<<' y '>>' en iostream.h. El siguiente ejemplo, simplificado, muestra una posible implementacin: class complejo { double x, y; 58 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ public: complejo& operator+= (complejo a){ x+= a.x; y+= a.y; return *this; } La funcin retorna una referencia a un "complejo" a travs del puntero 'this'. Los datos 'x' e 'y' del objeto que invoca la funcin no necesitan un 'this' explicito, esto ocurrira en caso de que el parmetro fuera, por ej, un entero 'x' o 'y'. La lnea final, que explicita el retorno de un puntero al mismo objeto que la invoco, es necesaria para detalles relacionados con la concatenacin de operadores, la modificacin de los datos privados ya se ha hecho en las dos lneas anteriores. 1.3.5 aritmtica de punteros Son posibles ciertas operaciones con punteros y los operadores de suma y resta (-,+,--,++). Siendo 'T' el tipo a que apunta el puntero, el siguiente cuadro sintetiza las distintas posibilidades y el tipo de resultado generado: Operacin Puntero a T mas +- entero Puntero a T menos entero Puntero mas puntero +Puntero a T menos puntero a T Resultado Puntero a T Puntero a T ------------Entero Comentarios Si el puntero apunta mas all del limite superior del array el resultado es no definido Si el puntero apunta mas all del limite inferior del array el resultado es no definido No permitido El resultado indica el numero de elementos T entre los dos punteros

Puntero entero

Puntero puntero

Los punteros son direcciones de memoria pero la aritmtica de punteros no es una simple suma o resta de direcciones. Estas operaciones estn adaptadas especialmente para tratar con arrays, de may que incrementar en 1 el valor de un puntero no apunte a la prxima direccin de memoria, sino al prximo elemento de un array. El nico caso donde 'prxima direccin' es igual a 'prximo 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. Puntero +- entero Es el tipo de operacin mas frecuente con punteros, especialmente porque el incremento del puntero en 1 permite recorrer un array elemento por elemento. Hay dos modos de realizar esto, el primero consiste en modificar el valor del puntero, y el segundo en direccionar el elemento igual a [puntero+ entero], procedimiento que tiene la ventaja relativa de no modificar el valor inicial del puntero, que seguir apuntando al mismo elemento del array. Por ejemplo: long k [4] = {35,34524,543594,354}; long* pk = k; int a;

Metodologa de la Programacin II

59

Tema 1 //Primer modo ------------------------------------for {a=0;a<4;a++) { printf("%ld",*pk); pk++; } //Segundo modo-------------------------------------for (a=0;a<4;a++){ printf("%ld, *(pk+a)); } En ambos casos se obtiene el mismo resultado, pero al salir del bucle el estado del puntero Serra diferente segn la modalidad adoptada. En el primer caso el puntero estar apuntando fuera del array (pk+4), en el segundo el puntero seguir apuntando al comienzo del array, pues las direcciones sucesivas se tomaban del valor temporal de (pk+a), sin afectar al valor del puntero. En el bucle de la segunda modalidad es importante la presencia del parntesis, la notacin *pk+a tendra un efecto por completo diferente, el operador '*' tiene mayor precedencia que '+', por lo tanto se tomara siempre el elemento *pk (el primer elemento del array) y luego se le sumaria 'a'. Debe 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 tomaramos los sucesivos valores (p+2). O si necesitramos obviar los primeros 'n' caracteres de una cadena, y copiar el resto en un buffer, podramos escribir: strcpy(buffer, cad+n); La suma de enteros a punteros tiene muchas aplicaciones, en especial si se combinan con las libreras standard de C y C++. En 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'. Esto puede depender en parte de cada compilador, pero como norma general lo que es indeterminado no es el valor-resultado del puntero (la direccin que almacena), sino el valor almacenado en tal direccin. Es decir, dado un puntero-resultado 'pr', por mas que ese valor desborde el array al que apuntaba, 'pr' Serra previsible mientras que '*pr' no lo Serra, se dice que su valor es 'indefinido'. 1.3.5.1 Puntero - puntero Esta operacin da como resultado no un puntero sino un valor entero. Es necesario que ambos punteros sean del mismo tipo, en caso contrario se producir un error en tiempo de compilacin. La resta de punteros tiene la propiedad de que su valor es independiente de los tipos implicados, es decir: dado un puntero 'p1' y otro 'p2' que apuntan respectivamente a los elementos 'n' y 'm' de un array, el valor entero de 'p1-p2' Serra igual a 'n-m', independientemente del tipo a que apunten los punteros. El 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. Por ejemplo: int t [] = {45,345,5,354,345}; 60 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ int* pt1 = &t[1]; int* pt2 = &t[4]; int res = pt2-pt1; //en vez de &t[2] se puede t+2;

El valor de 'res' Serra 3, mientras que en termino de direcciones de memoria la distancia entre ambos punteros es de 6, pues cada entero ocupa 2 bytes. No es necesario involucrarse con demasiados detalles de lo que sucede en memoria, los punteros y su aritmtica estn adaptados para tratar con direcciones de modo implcito. La resta de punteros no es tan frecuente como las operaciones de incremento pero puede prestar usos valiosos, sobre todo en relacin a cadenas de caracteres y junto a libreras standard de funciones. Supongamos que necesitramos una rutina que extraiga una subcadena de una cadena dada, y que la subcadena estuviera determinada por dos caracteres delimitadores, por ej 'ch1' y 'ch2', el esquema de una funcin de extraccin seria el siguiente: Se setean dos punteros (p1 y p2) apuntando a los delimitadores 'ch1' y 'ch2'. funcin que realiza strchr() En un bucle se extraen (p1-p2) caracteres a partir de 'ch1'.

En general se recomienda que la aritmtica de punteros se realice en el nivel mas simple posible. La principal fuente de error proviene de desbordar los limites (inferior o superior) del array al que apunta el puntero. Estas dificultades no se presentan si se esta tratando con mapeo de memoria, flujos de bytes, anlisis sintctico a nivel compilador o rutinas similares de bajo nivel, pero cuando los datos presenten mayor nivel de estructuracin sern necesarias mayores precauciones. Itinerar en un array Supongamos una cadena 'cad' y un puntero que apunta a esa cadena. char cad [] = "hola"; char * ptr = cad; El puntero 'ptr' apunta al primer elemento de 'cad'. Si ahora incrementamos el puntero: ptr++; este apuntara a cad[1], es decir el segundo byte de cad, y si sacamos en pantalla 'p' y '*p' se vera lo siguiente: printf ("%s", p); printf("%c", *p); // sale "ola" // sale 'o'

Como hemos visto antes, printf() saca en pantalla todo lo que haya desde el char al que apunta el puntero recibido como parmetro hasta el primer '\0' que encuentre. La explicacin de lo sucedido es la siguiente: 'p' contiene (como cualquier variable) un valor, este valor es una localidad de memoria, por ej 0xfff2, al incrementar el puntero con p++ lo que hacemos es incrementar el valor que contiene, por eso pasara de 0xfff2 a 0xfff3. Si el puntero hubiera sido tipo entero, el incremento 'p++' habra sumado en 2 la localidad apuntada (0xfff4), pues un entero ocupa 2 bytes de memoria. Metodologa de la Programacin II 61

Tema 1 El mecanismo es simple y muy eficaz para itinerar a travs de un array, pero no solo eso, tambin nos permite itinerar por cualquier zona de memoria y es el mtodo mas cmodo para hacerlo. 1.3.5.2 Mapear localidades de memoria Casi siempre se mencionan a los punteros en relacin a arrays, pero un puntero puede operar de modo totalmente independiente de cualquier array, precisamente para itinerar libremente por regiones de memoria, segn Stroustrup (1995), "la implementacin de punteros tiene por finalidad mapear directamente los mecanismos de direccionamiento de la maquina en que se ejecuta un programa ". Veamos un ejemplo. Supongamos que nuestro programa opera en modelo small (como la mayora de los ejemplos dados) y que queremos observar el estado del segmento de datos-stack durante la ejecucin del programa. Una funcin como la siguiente podra cumplir esa funcin: int funcin() { char *tt=0; unsigned char ch; int a,fil,col;

//Apunta al comienzo del segmento de datos //Unsigned, para no lidiar con valores negativos

for (a=0;a<256;a++) { ch = *(tt+a); //Para ver el final del segmento seria: ch=*(tt+0xff00+a); col = a%16; fil = a/16; gotoxy(col*3+24,fil+4); printf("%02X",ch); //Representacion hexadecimal if (ch<32) ch=46; //Si ch <32 se reemplaza con puntos gotoxy(col+2,fil+4); printf("%c",ch); } return 0;}

//Representation ascii

La funcin saca en pantalla los primeros 256 bytes del segmento de datos-stack. Los detalles del bucle de impresin son para que la salida sea similar a la de un editor hexadecimal, en una columna los caracteres ascii, excluyendo a aquellos cuyo valor es menor a 32 (0x20), en realidad muchos de estos caracteres se pueden imprimir bien, mientras que es mejor evitar algunos como 7,8,10,13, pero se han evitado todo los menores a 32 para simplificar el cdigo. En otra columna se exhibirn los valores hexadecimales de esos caracteres. El 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 funcin. Para observar tal sector, con el cdigo anterior, basta con reemplazar la primera lnea debajo del bucle por la indicada en el comentario. El esquema de la funcin, aplicada a lectura de ficheros (archivos), podra ser de utilidad en una salida a pantalla de un editor hexadecimal.

62

Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ 1.3.5.3 Paso de parmetros en funciones Por default las variables declaradas dentro de una funcin no estn disponibles para otras funciones. Cuando necesitamos que una funcin acceda a datos de otra el principal recurso es el paso de argumentos. El paso de argumentos se hace principalmente a travs de la pila (stack), un bloque de memoria especializado en el almacenamiento de datos temporales. El espacio total disponible para uso de la pila varia segn el modelo de memoria utilizado por el programa (aparte de las limitaciones de hardware), si nuestro programa utiliza el modelo 'small' de memoria el espacio total para uso de pila y datos Serra de 64 Kb. Cuando se guardan en la pila mas valores de los que caben se produce un 'stack overflow', un desborde de pila. Las funciones recursivas trabajan haciendo una copia de si mismas y guardndola en la pila, motivo por el cual no es raro encontrar desbordes de pila provocados por recursiones mal calculadas. Hay muchos motivos para utilizar la pila del modo mas econmico posible, y los punteros cumplen una gran utilidad en este caso. Un parmetro puede ser pasado a una funcin de dos modos diferentes: por valor y por referencia. Pasarlo por valor implica que la funcin receptora hace una copia del argumento y trabaja con ese doble del original, cualquier modificacin realizada en la variable-copia no producir ningn cambio en el parmetro enviado. En cambio al pasar un valor por referencia estamos pasando la direccin 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 enva el dato y la que lo recibe. Una variable comn puede ser pasada por valor o por referencia. En el siguiente ejemplo la funcin 'f ' recibe dos parmetros pasados por la funcin 'principal', el primero es pasado por valor y el segundo por referencia. En el primer caso 'f ' incrementara el valor de la variable 'a' que es una copia local del argumento 'x', ese incremento afectara a 'a' pero no a 'x'. En 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'. Tanto la variable 'y' de la primera funcin como 'b' estn asociadas a la misma localidad de memoria (hay dos nombres para una misma localidad de memoria). void f (int a, int& b) { a++; b++; } //--------------------------void principal() { int x = 1; int y = 1; f(x,y); ................... Un array en cambio siempre es pasado por referencia, la funcin que recibe el parmetro recibe un puntero al elemento inicial del array. Por ejemplo: Metodologa de la Programacin II 63

Tema 1 int xstrlen (char* str) { int a=0; while (*str++!=0) { a++; } return a; } //----------------------------void principal () { char cad[] = "hola"; printf ("%d", xstrlen(cad)); .......................................

//alternativa-->

while (str[a]!=0) {a++;}

En el ejemplo, la funcin xstrlen() nos da el largo de un array de caracteres buscando la posicin del '\0' de fin de cadena, y la funcin 'principal' sacara ese valor entero en pantalla. Obsrvese la sintaxis alternativa para el bucle, en un caso anotamos 'str' como puntero y en el otro como 'array', ambas notaciones son intercambiables. Al pasar un array por referencia, la funcin receptora solo recibe la direccin inicial del array, es decir 2 bytes, por lo tanto pasar un array de 30 KB consume menos recursos de stack que pasar una variable de tipo long (pasada por valor), que requiere 4 bytes. El principal inconveniente de pasar un parmetro por referencia radica en la posibilidad que tiene la funcin receptora de alterar todos los datos del parmetro, por esta causa es frecuente que, en la declaracin de la funcin, tal parmetro se declare como "const" para evitar la corrupcin accidental de ese dato. 1.3.5.4 Reserva de Memoria dinmica En primer lugar recordemos que es la 'memoria dinmica'. Hay tres formas de usar la memoria en C++ para almacenar valores: Memoria esttica. Es el caso de las variables globales y las declaradas como 'static'. Tales objetos tienen asignada la misma direccin de memoria desde el comienzo al final del programa. Memoria automtica. Usada por los argumentos y las variables locales. Cada entrada en una funcin crea tales objetos, y son destruidos al salir de la funcin. Estas operaciones se realizan en la pila (stack). Memoria dinmica. Tambin llamado 'almacenamiento libre' (free store). En 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. La operacin de reservar y liberar espacio para variables globales, estticas o locales son realizadas de modo implcito por el programa, la nica modalidad que requiere mayor atencin por parte del programador es la de reservar memoria en forma dinmica. 64 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ El papel de los punteros en relacin a la memoria dinmica es muy importante, por la razn de que al pedir, al sistema operativo, una cantidad determinada de memoria dinmica para un objeto, el sistema nos retorna un puntero que apunta a esa zona de memoria libre, la respuesta depender de si hay o no tanto espacio como el solicitado. Si hay suficiente memoria se retorna un puntero que apunta al comienzo de esa zona de memoria. Si no hay suficiente, retorna un puntero nulo.

En C++ los operadores usados para requerir y liberar memoria dinmica son new y delete. La sintaxis es la siguiente: Variable individual Array de elementos individuales Reserva de memoria dinmica int* a = new int int* a = new int [n]; liberacin de memoria reservada delete a; delete [] a; Nota: las primeras versiones de TurboC++ no admiten la lnea "delete [ ] a" con corchetes vacos, para liberar memoria dinmica de un array requiere que explicitemos cuantos elementos hay que borrar, explicitando este valor entre corchetes. En la versin TurboC++3.0 esta caracterstica de la sintaxis standard ya se encuentra implementada. Las ventajas de utilizar memoria dinmica se valoran mejor en comparacin con las caractersticas de la reserva esttica de memoria. Reserva esttica de memoria Los objetos locales son creados al Creacin entrar en la funcin que los de declara. Los globales son creados objetos al iniciarse el programa. Los objetos locales se destruyen al Duracin salir de la funcin en que han sido de los creados. Los globales, al salir del objetos programa. Al reservar memoria esttica para un array el valor del indice debe ser un valor constante. Indice de Ej: arrays int n [20]; int n [variable no const]; //no permitido Reserva dinmica de memoria La memoria se reserva explcitamente mediante el operador new.

Los objetos necesitan ser destruidos explcitamente, con el operador delete. El indice de un array puede ser un valor variable, de modo que la cantidad de memoria reservada por una lnea de cdigo puede variar en tiempo de ejecucin (runtime). Ej: int* n = new int [variable no const] //correcto

La estrecha relacin que existe entre arrays y punteros explica que la solicitud de memoria dinmica para un array culmine en la devolucin 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. Metodologa de la Programacin II 65

Tema 1 Los mecanismos de bajo nivel que implementan el uso de memoria dinmica son bastante complejos y no nos detendremos en ello. Desde el punto de vista del programador, la principal fuente de errores se deriva de una mala coordinacin entre operadores new y delete, sea que olvidemos liberar la memoria que ya no utilicemos, o que intentemos borrar, o utilizar, un objeto ya borrado. 1.3.6 Punteros no inicializados La sola declaracin 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 direccin de memoria: es decir 2 o 4 bytes. La siguiente lnea de cdigo tiene ese efecto. char * ptr; Declarar un puntero de ese modo no es ningn error, el error es olvidar lo siguiente: Que se trata de un puntero 'no inicializado', que puede estar apuntando a cualquier localidad de memoria, tal vez alguna en la cual sea errneo escribir algn dato (por ej: el comienzo del segmento de datos). Que no estamos reservando ningn espacio extra para asociar un array, una estructura o un objeto a ese puntero.

Es suficiente continuar la lnea anterior con una de las siguientes: *ptr = 'a'; strcpy (ptr, "hola"); para cometer un error, es probable que al final del programa aparezca el mensaje 'Null pointer assignment', indicando que se ha sobrescrito una zona 'prohibida' del segmento de datos. Los 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. Si el puntero hubiera sido inicializado por ej, apuntando a un array, entonces el primer problema, adonde apunta, estara solucionado. Pero aun podramos olvidar el segundo, el espacio de memoria reservado debe ser suficiente. Por ejemplo: int main() { int x = 4; //Variable cuyo valor Serra destruido en este ejemplo char cad [] = "hola"; //Reserva esttica de 5 bytes (4 mas 1 del '\0') para 'cad' char* ptr = cad; //ptr apunta a cad[0] strcpy(ptr, "casa"); //bien, 'casa' tiene 4 bytes de texto, no excede a 'hola' strcpy(ptr, "Buen dia"); //Mal!, esta cadena excede la capacidad de 'cad'. ........................etc

66

Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ La ultima cadena se copiara de todos modos en la direccin apuntada por 'ptr', desbordando la capacidad del array 'cad' para almacenar ese dato, como consecuencia el valor de la variable 'x' Serra destruido. Se trata de un error que no es difcil 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 cad[7]='a', son exactamente los mismos que los de hacer lo mismo con el puntero, solo que por alguna razn es mas fcil cometer el error con el puntero olvidando la capacidad del array al que apunta. En el siguiente ejemplo se visualizara lo que sucede con las localidades de memoria implicadas cuando se produce un error de sobre escritura como el antes mencionado. int main () { int x = 5, y = 4, z = 3; char cad[] = "abcde"; char* ptr = cad;

strcpy(ptr,"Hasta luego");

Como puede observarse se han perdido los valores originales de las tres variables enteras. Las consecuencias concretas de sobrescribir variables dependen enteramente de lo que haga el resto del programa, en todo caso se trata de un error. Por 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 'wild pointers' (punteros salvajes). 1.3.6.1 Punteros y literales de cadena Un literal de cadena es un conjunto de caracteres encerrados entre comillas, por ejemplo la cadena "Hasta luego" del ejemplo anterior. Los 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. Con la lnea siguiente: char* ptr = "hola"; se crean dos entidades, no una. Por una parte se reservan 2 bytes para el puntero 'ptr' (para almacenar una direccin), pero tambin es creada otra entidad, un literal de cadena, que es constante, su contenido aqu es 'hola' y no es modificable en el transcurso del programa. Los bytes reservados por esta lnea de cdigo son:

Metodologa de la Programacin II

67

Tema 1 2 para que el puntero almacene una direccin (aqu la direccin donde se encuentra el literal 'hola') 5 para el literal.

Es importante comprender que un 'literal de cadena' es una entidad diferente a un array de caracteres o a un puntero a char*, es un valor constante y su contenido se almacena en un sector especial del segmento de datos, en la parte inicial del mismo. En los casos en que un puntero es inicializado con un 'literal de cadena' el error es tomar el puntero e intentar copiar algo desreferenciandolo. Algunos compiladores darn un mensaje de error en tiempo de compilacin, otros mas antiguos pueden permitir tal copia. Lo recomendable, cualquiera sea el compilador, y permita o no modificar el valor apuntado, es no intentar modificar el contenido apuntado por 'ptr' en ningn caso. Si se necesita modificar el contenido lo mejor es copiar el literal en un array, reservando la memoria suficiente, y operar sobre el array. Ligar 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. Cuidando estos detalles, declarar un puntero a literales puede ser muy cmodo para manejar cadenas constantes, como las que conforman mens, en estos casos un array de punteros es un buen recurso. char* menu1[] = {"Archivo", "Abrir", "Nuevo", "Guardar", "Guardar como...", "Salir"}; Esto es mas fcil de manejar que un array multidimensional, con "men[n]" 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. Si las cadenas necesitaran ser modificadas habr que implementarlo de otro modo, por ejemplo asignando memoria dinmica para su almacenamiento. 1.3.6.2 El mensaje "Null pointer assignment" Un progreso importante en el manejo y comprensin de bugs y mensajes de error es nuestra capacidad de reproducirlos de modo previsible. Por alguna razn el mensaje "Null pointer assignment" es uno de los que mas cuesta reproducir, posiblemente porque existe cierta confusin en torno a la nocin de 'puntero nulo'. La traduccin literal del mensaje de error seria: "asignacin de puntero nulo", o en otros terminos: "se ha asignado un valor a un puntero que es nulo". A continuacin veremos en detalle que significa esto, cuales son los pasos que lleva a cabo un compilador (aqu TurboC++1.01) para emitirlo, como podemos reproducirlo de manera controlada, y por ultimo que cuidados podemos tener para evitarlo. Antes que nada recordaremos que es un puntero nulo: es un puntero que apunta a un sitio donde no debe (pero si puede) estar almacenado ningn dato, por as decir, se trata de una zona 'prohibida', se puede leer pero no setear valores all. De este modo, cuando una funcin que retorna punteros retorna un puntero "Null", se usara esto como significando 'nada'. Por ejemplo: la funcin strchr() busca un carcter dentro de una cadena, si lo encuentra retorna un puntero a ese carcter, si no lo encuentra retorna un puntero nulo. Ahora bien, un puntero apunta siempre a algn sitio, y el 'puntero nulo' no es una excepcin. Con un compilador Borland y modelo de memoria small o mdium un puntero nulo apunta a la direccin 0 del segmento de datos, es decir 68 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ DS::0000. Escribir all un valor que no sea 0 garantiza la presencia del mensaje de error, pero no es la nica localidad de memoria que lo produce. Las condiciones para producir el mensaje de "Null pointer assignment" son las siguientes: El modelo de memoria utilizado por el programa es SMALL o MEDIUM. Se sobrescribe algn valor del comienzo del segmento de datos-stack. Es decir el intervalo compuesto por 4 bytes con valor 0 (cero) mas el copyright de Borland.

Si se modifica algn valor a partir de la localidad DS::0x3d, donde comienza el mensaje "Null pointer..." ya no se produce el mensaje de error, de hecho solo las localidades resaltadas con color pueden producirlo. Con mas exactitud habra que decir que no es el hecho de 'escribir' all 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. Por lo tanto si uno sobrescribe 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. Esto ultimo solo a titulo informativo, no es recomendable de ningn modo intentar operar sobre esas localidades de memoria. Los siguientes ejemplos ilustran algunos modos de generar el mensaje de error. Ejemplo N:1 - Desreferenciacion de un 'wild pointer' int main () { char* p; *p = 'a'; return 0; } No esta determinado a donde apuntara 'p', pero en los ejemplos observados apunta siempre a 0x0000 o 0x000c, ambos bytes 'prohibidos', al darle el valor 'a' modifica el comienzo del segmento y aparece el mensaje de error que estamos estudiando. Ejemplo N:2 - No requiere comentarios, sucede lo mismo que en el ejemplo anterior. #include <string.h> int main() { char *p; strcpy (p, "wxsjkwe"); return 0; Metodologa de la Programacin II 69

Tema 1 } Ejemplo N: 3 - Olvidar que una funcin ha retornado un puntero nulo #include <string.h> int main () { char* p; char ch = 'a'; char cad[] = "jorge"; p = strchr(cad, ch); //buscamos 'ch' dentro de 'cad'. ................... *p = 'x'; //Como 'ch' no esta en 'cad' strchr() retorno un puntero nulo, ....................etc //que ahora es desreferenciado y escrito. En este ejemplo hemos invocado una funcin, la misma retorno un puntero nulo y luego, y sin redireccionar el puntero, le damos un valor, como resultado se escribe 'x' en el primer byte del segmento provocando el mensaje de error. Algo similar ocurrira si al solicitar memoria dinmica 'new' retornara un puntero nulo, y operramos con el mismo sin antes comprobar el xito de la solicitud. Existen muchsimos modos de producir el mensaje de error, pero todos se basan en lo mismo. Una observacin mas: el mensaje de error nos avisa que hemos escrito en un puntero 'nulo', en este contexto eso significa un puntero que apunta a 0x0000, si apuntara a 0x0001 ya no seria un puntero nulo. Sin embargo hemos visto que no es esa la nica localidad de memoria que produce el mensaje de error, se trata mas bien de un intervalo de 45 bytes, desde 0x0000 hasta 0x002c, por lo tanto no es solo la 'asignacin de un puntero nulo' la que provoca el mensaje, aunque as lo da a entender "Null pointer assignment", por lo menos as sucede en los compiladores Borland. 1.3.6.3 "Dangling pointers" Este tipo de problemas suscita muchas preguntas en las diversos foros (o Faq's) sobre C y C++, y se presenta con frecuencia en funciones que retornan un puntero. La causa del problema es esta: la funcin retorna un puntero que apunta a una variable o array declarados como locales, al salir de la funcin todas las variables locales son 'deallocated', se pierde la conexin entre direccin de memoria y variable, la zona de memoria que utilizaban es liberada, por lo tanto el puntero (al salir de la funcin) apunta a una 'zona liberada', no ligada con ningn array o variable. La siguiente funcin 'f1' reproduce el problema: char * f1() { char buffer[128]; //Reserva de memoria esttica para variable local cout << "Entre su nombre: "; cin.getline( buffer, 128 ); return buffer; //Retorna como puntero de variable local } int main(){ char* ptr; .................. 70

//Resto del cdigo aqu Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ ptr = f1(); f2(); //El puntero ptr recibe la direccin de 'buffer' //Llamado a una funcin 'f2' cualquiera

El puntero 'ptr' recibir la direccin 'correcta', la misma en que estaba almacenada la cadena 'buffer', el problema es que 'buffer', al ser declarada como local, pierde su localizacin de memoria. El rol que juega la stack (pila) en el llamado a funciones se ilustra en el siguiente grafico:

Vamos a comentar paso a paso la relacin entre stack, funciones y el cdigo anterior. I- Al 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 Serra liberado al terminar el programa. Hasta ese momento los datos locales de las restantes funciones 'no existen', en el sentido de que no tienen localidades de memoria donde almacenar un valor. Distinto 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. II- La funcin main ( ) llama a la funcin 'f1'. Como 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 'f1', en trminos de ensamblador diramos 'la pila crece (hacia abajo) seteando un nuevo valor de BP (bass pointer) y SP (stack pointer)'. En esas localidades de memoria, estarn los valores de 'f1' hasta que salgamos de la funcin. III- Salimos de la funcin f1 retornando un puntero a una variable local. Lo que el puntero retorna es, obviamente, una direccin de memoria, esa direccin se mantiene, no es borrada ni se pierde. El problema no es el puntero, el problema es que se pierde la variable local. Que sucede con las variables locales al salir de la funcin? Mientras no se llame a otra funcin sus valores pueden perdurar, pero no es algo que un compilador garantice. IV- Llamamos a otra funcin. En ese momento las localidades de memoria asociadas a las variables de la anterior funcin 'f1' sern sobrescritas por las variables locales de la nueva funcin llamada 'f2'. Nuestro puntero a la variable local de f1 seguir apuntando a la misma localidad de memoria, pero su contenido Serra indeterminado, y Serra muy peligroso usarlo para cualquier propsito (a menos que sea reasignado). La regla practica seria esta: no retornar nunca un puntero que apunte a una variable Metodologa de la Programacin II 71

Tema 1 declarada como local. Pero entonces, que camino seguir para retornar un array o puntero de modo seguro desde una funcin? En la literatura existente sobre el tema se analizan y recomiendan tres posibles soluciones, todas apuntan a preservar el valor de la variable, evitando que sea 'local': Declarar a la variable 'static' Reservar memoria dinmica para la variable dentro de la funcin Retornar el valor utilizando un parmetro de la funcin llamadora.

Se 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. Al declarar una variable como 'static' le estamos reservando un sitio especial dentro del segmento que no Serra alterado por el flujo general del programa, ese sitio es en la parte baja de la stack, lejos de la parte alta donde se produce todo el movimiento de variables locales de las distintas funciones. Para esto basta con anteponer 'static' a la declaracin de la variable: static char buffer[128]; El nico inconveniente es que esa zona de memoria no Serra liberada en todo el transcurso del programa, la cantidad de bytes reservados determinara si este recurso es demasiado costoso o no. Reservar memoria dinmica dentro de la funcin llamada. En nuestro ejemplo seria: char* buffer = new char [128]; aqu Serra responsabilidad del programador liberar la memoria reservada, en caso contrario se producirn 'fugas de memoria' (memory leaks), es decir, memoria fuera de uso que no puede ser reutilizada para almacenar nuevas variables. Se trata de un tema tcnicamente complejo, al punto de que muchos compiladores no son enteramente eficaces en la liberacin de memoria reservada dinmicamente (tardan en hacerlo), existe software 'recolector de basura' (garbage collection) cuya funcin es liberar zonas de memoria a las que ya no puede acceder ninguna variable en tal punto de un programa. Devolver el valor a travs de un parmetro. La variable de retorno no se declara dentro de la funcin que retorna, sino en la funcin que llama. Por ejemplo: void f1 (char* buff) { .................. } int main () { ............... char buffer[128]; f1(buffer); .................etc,

72

Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ No es necesario retornar explcitamente la variable pues el parmetro ha sido pasado 'por referencia', 'buff' de 'f1' apunta a la misma localidad que 'buffer' de 'main', y pueden ser tratados como un mismo puntero. El nico defecto del mtodo radica que puede disminuir ligeramente la legibilidad del cdigo, la funcin retorna un puntero pero de modo disimulado, el tipo (type) de la funcin no nos informa nada al respecto. Las funciones de las libreras de C y C++ utilizan en general las dos ultimas alternativas, (2) y (3). 1.3.7 Memoria dinmica Las operaciones de reservar memoria dinmica y liberarla, con new y delete, estn enteramente en manos del programador, esto proporciona gran flexibilidad de recursos pero tambin oportunidad para diversos tipos de errores. Cada vez que aparece el operador new en relacin a un objeto debera haber una aplicacin del operador delete a ese mismo objeto. Los problemas suelen generarse por dos modalidades de error: No liberar la memoria dinmica reservada para un objeto. Intentar borrar, o desreferenciar, un objeto ya borrado.

1.3.7.1 Memoria no liberada La memoria reservada dinmicamente necesita ser liberada de modo explicito con delete. Si existe un objeto que ya no usamos, y que fue almacenado dinmicamente, nos encontramos frente a una 'fuga de memoria' (memory leak), el caso mas critico se presenta cuando ya no es posible acceder al objeto, pues no Serra posible borrarlo. Durante la ejecucin 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) { char* p = new char [a]; return 0; } La funcin no hace nada interesante, es simplemente el esquema de un error posible. La memoria reservada para el puntero 'p' no ha sido liberada al salir de la funcin, la direccin apuntada por 'p' se pierde, y no Serra posible liberar esa memoria en ningn sitio. Es el modo mas simple de producir una fuga de memoria. habra sido necesario agregar la lnea: delete [] p; antes de salir de la funcin para que ese monto de memoria hubiera sido liberado. Mientras que es tcnicamente posible reservar memoria en una funcin y liberar esa memoria en otra funcin, se considera una practica riesgosa, por la posibilidad de olvidar quien tiene la responsabilidad de liberar memoria. Una posible solucin es reservar memoria para el objeto en la funcin llamadora, pasar el objeto como parmetro (por referencia) y retornarlo, as la Metodologa de la Programacin II 73

Tema 1 responsabilidad de reservar y liberar memoria respecto al objeto estar en manos de una misma funcin. El esquema seria el siguiente: int f (char* c) { ............... return 0; } int main() { int b = 34; char* t = new char[b]; f(t); ............... delete [] t; ............... Otro modo de provocar problemas es reservar memoria dinmica por segunda vez para un puntero, antes que haya sido liberada la primer reserva. Por ejemplo: void f (int a, int b) { char* p = new char[a]; ...................... p = new char[b]; ..................... aqu la memoria reservada por el primer uso de new ya no podr ser liberada, pues se ha perdido su direccin. Toda segunda asignacin del puntero 'p' sin antes liberar la memoria dinmica asociada a el, producir fugas de memoria. Otra variacin del mismo problema es el siguiente: void f (int a) { char* p = new char[a]; char* q = new char[a]; ...................... p = q; ..................... delete [] p; delete [] q; } Este esquema de error es importante pues, como se vera mas adelante, se presenta en forma velada en problemas con constructores de objetos. El error es reasignar el puntero 'p' antes de liberar la memoria por el reservada. Esto tiene dos consecuencias negativas: 74 La memoria reservada originalmente por 'p' no podr ser liberada. La memoria reservada por 'q' Serra liberada dos veces (muy problemtico). Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Un modo de fallar en liberar acertadamente la memoria reservada con new, en relacin a un array, consiste en aplicar el operador delete, olvidando los corchetes entre el operador y el nombre del puntero. int f () { char* p = new char[100]; ........................ delete p; //error, solo libera un elemento de 'p' delete [] p; //bien, libera el array apuntado por 'p' 1.3.7.2 Operar con un objeto ya borrado La segunda familia de problemas se produce por intentar desreferenciar o usar un puntero al cual ya se ha aplicado el operador delete. El principal recurso para evitar este problema es (una vez aplicado el operador delete) setear este puntero a NULL, esto protege contra posteriores usos equivocados de delete, pues por convencin la aplicacin de delete a un puntero nulo no tiene ningn efecto. Tambin es un error desreferenciar un puntero al que se ha aplicado delete sin antes asignarle una nueva direccin. La razn 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 dinmicas. La solucin es reasignar una direccin al puntero antes de desreferenciarlo, norma general que tambin soluciona el problema de los punteros nulos. Los pasos correctos se ejemplifican en el siguiente cdigo: int f () { char cad[] = "hola"; char* p = new char[40]; //Primera reserva de memoria dinmica ....................... delete [] p; //liberacin de mem dinmica p = NULL; //Precaucion por posible sobreborrado de 'p' ....................... p = cad; //Nueva asignacin *p = 'a'; //Desreferenciacion de 'p' Nunca se debe desreferenciar un puntero sin antes asignarle un valor. 1.3.7.3 Datos miembros punteros y copia de objetos Los datos miembro de un objeto pueden ser inicializados mediante un constructor, una inicializacin de copia, o asignacin de copia. Suponiendo la existencia de una clase llamada "Clasex" veamos las siguientes lneas: Clasex a; Clasex b; Clase c; Metodologa de la Programacin II 75

Tema 1 Clasex d = b; //inicializacin de copia (constructor copia) c = a; //asignacin de copia La sola declaracin de los objetos 'b' y 'a', sin parmetros, invoca un constructor por defecto. En la tercera lnea no se invoca al constructor, se realiza una copia del objeto 'b' en el objeto 'a'. A menos que se especifique algo distinto, esta copia (llamada 'asignacin de copia'), produce una replica miembro a miembro de los datos privados de 'b' en los datos privados de 'a'. A primera vista esto es muy natural y no problemtico, pero si entre los datos privados figuran punteros entonces pueden plantearse importantes problemas. class Clasex { int x; char ch; char* cad; public: Clasex (int n = 40) { cad = new char [n]; } //Constructor default ~Clasex () {delete [] cad;} //Destructor .............. } void f() { Clasex a; //Invoca constructor Clasex b = a; //inicializacin de copia - Problemas con el puntero! Clase c; //Invoca constructor c = b; //asignacin de copia - Problemas con el puntero! } aqu hay tres objetos "Clasex" en juego. Se trata de objetos locales, por lo tanto el destructor Serra invocado de modo automtico tres veces al salir de la funcin. El primer problema se plantea cuando tomamos conciencia de que, en la funcin "f ( )", el constructor es llamado solo dos veces (el destructor: tres veces), tanto la inicializacin de copia como la asignacin de copia no utilizan el constructor, sino que copian datos miembro a miembro. El esquema de los tres objetos seria el siguiente: 'a' 'b' 'c' Datos privados. a.x b.x c.x Una copia individual a.ch b.ch c.ch por cada objeto a.cad b.cad c.cad Funciones publicas: Clasex::Clasex (int); una copia para todos Clasex::~Clasex(); los objetos La copia de los datos 'x' y 'ch' no presenta ningn problema, cada objeto tiene su 'x' y su 'ch' en distintas localidades de memoria, copiar estas variables es copiar el valor almacenado. Cada objeto tiene tambin una localidad de memoria para 'su' puntero 'cad', el problema es adonde apuntan esos punteros. 76 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ La copia de punteros (ej, a.cad = b.cad) es copia de las direcciones a la que apuntan. Pero esas direcciones, en nuestro ejemplo, son localidades de memoria reservadas mediante memoria dinmica, por lo tanto, luego de: a = b; Suceder que los punteros "cad" de ambos objetos apuntaran a la misma zona de memoria reservada con new, y es aqu donde se presenta el problema. Hay tres objetos y por lo tanto tres punteros, cada puntero debera tener sus propios bytes reservados (el default para nuestro ejemplo es 40). Una llamada comn al constructor reserva esos bytes, y son diferentes para cada objeto, pero una asignacin de copia hace que un puntero deje de apuntar a 'su propia zona' y apunte a los mismos bytes que el otro puntero. Como consecuencia: Una zona de memoria queda fuera de alcance, no pudiendo ser liberada y se crea una fuga de memoria Dos punteros apuntan a la misma zona, cuando se invoque el destructor, este liberara dos veces una misma zona de memoria, lo que es muy problemtico.

La solucin. Cuando entre los datos privados hay punteros es necesario explicitar un constructor de copia diferente al default, para evitar la copia entre punteros, y es necesario tambin proveer de un asignador de copia diferente al default, esto significa que para disponer de la notacin "a = b" Serra necesario sobrecargar el operador '=' y darle un sentido diferente, que evite la copia de punteros. La naturaleza del problema puede aclararse con un cdigo que no utiliza clases pero que presenta el mismo error. void f () { char* a = new char[40]; char* b = new char[40]; a = b; //Error! no se debe reasignar sin antes liberar memoria delete [] b; delete [] a; } Cada puntero tiene 'sus' 40 bytes de memoria dinmica, al reasignar "a=b" el puntero 'a' deja de apuntar a la zona de memoria reservada con 'new', esa direccin se pierde y no podr ser liberada (fuga de memoria). Por otra parte, las dos invocaciones de 'delete' cometen el error de liberar una misma zona de memoria. 1.3.8 Que es una cadena de caracteres en C/C++? En cualquier lenguaje de programacin las cadenas de caracteres tienen una importancia especial, no solo porque es el tipo mediante el cual se almacenan los mensajes a pantalla o entradas del teclado, sino porque un carcter (char) es del tamao de un byte, y un byte es la menor unidad de informacin 'natural' para la maquina. Una cadena de caracteres es una coleccin ordenada de Metodologa de la Programacin II 77

Tema 1 bytes. Un fichero (archivo), la informacin de pantalla en el sector de memoria de video, las entradas de bytes por los puertos y muchas otras entidades se pueden conceptualizar cmodamente como esto: una coleccin ordenada de bytes. Es cierto que en muchos casos se adoptan tipos definidos o clases, para una mejor administracin de datos, pero aun en estos casos tales estructuras de datos complejas suelen utilizar arrays de tipo char en su nivel mas elemental. Hay lenguajes que tienen un tipo (type) preestablecido para tratar con cadenas de caracteres, es as en las distintas versiones de basic, donde el tipo 'String' es un tipo mas (como 'integer'), y as como existen arrays de enteros hay arrays de strings. En cambio en C/C++ podra decirse que el tipo 'String' no existe como tal (en C++ hay implementaciones de la clase 'String' pero no pertenecen propiamente al lenguaje sino a libreras anexas, dependiendo de cada compilador), el recurso usado por la mayora de las funciones de estos lenguajes es representar una cadena de caracteres como un array de elementos tipo char, un array de caracteres. El solo hecho de que una cadena (string) sea un array plantea dudas a quien viene de otros lenguajes, por ej: como representar entonces un array de strings? (un array bidimensional no es cmodo de manejar), cual es el largo permitido de una cadena de caracteres? que sucede si ese largo se modifica? como conocer en tiempo de ejecucin el limite de ese array? etc. Una cadena de caracteres, representada en memoria, es una simple sucesin de bytes, cada carcter se corresponde con un byte, si queremos sacar en pantalla una cadena de caracteres, el problema es: como sabe el programa donde finaliza esa cadena?, cual es su ultimo byte? A esta pregunta diferentes lenguajes plantean diferentes respuestas, segn un modelo de 'string', los dos modelos clsicos son el de Pascal y el de C. En Pascal el primer byte es reservado para almacenar el largo de cadena, es decir que la cadena de caracteres propiamente dicha comienza en la segunda posicin. Si solo hay 1 byte de espacio para almacenar el largo el mximo permitido para una cadena Serra de 255 bytes. Para cadenas de mayor extensin habr que utilizar algn otro recurso. En C se reserva una funcin especial al carcter cuyo valor ascii es 0 (cero), ese carcter indicara con su presencia que la cadena finaliza all, ese char forma parte de la cadena pero por convencin no se lo tiene en cuenta al determinar el largo de la cadena. De una cadena como "hola" se dice que tiene 4 caracteres, aunque en memoria luego de 'a' se encuentre el '\0' que forma parte de ella.

En memoria esa celda que indica el fin de cadena tendr el valor 0, insistimos en que no se trata de un elemento extra (como el EOF de ficheros) sino del char que en el juego de caracteres ascii corresponde al cero. Ambos modelos de cadena presentan ventajas e inconvenientes. En el modelo 'Pascal' es muy rpida la operacin de encontrar la longitud de cadena (se consulta el primer byte) mientras que en C/C++ hay que recorrer toda la cadena en bsqueda del char '\0'. Por otra parte una cadena tipo C no tiene ninguna limitacin en longitud (salvo las indicadas por el modelo de memoria utilizado o razones de hardware) mientras que en Pascal tendr un limite dictado por el byte que almacena el largo. El rol que juega el carcter '\0' es absolutamente esencial para comprender y manejar fluidamente cadenas de caracteres en C y C++, y la mayor parte de los problemas y bugs tienen relacin con accidentes y descuidos en relacin a ese carcter. 78 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Seria un error creer que en C/C++ el largo de la cadena esta registrado en algn sitio y que luego, en un segundo momento, el programa sita un '\0' en esa posicin, no!, el largo de cadena no existe como dato en ningn sitio. Existe una funcin standard que nos retorna un entero con el largo de una cadena enviada como parmetro, es strlen(char*), lo que hace esta funcin es simplemente contar caracteres hasta que encuentra el '\0', para una masa muy grande de bytes se podra pensar en una funcin menos costosa, pero strlen funciona de ese modo. 1.3.8.1 Array y cadena de caracteres: algunas diferencias En muchos textos sobre C y C++ se encuentra la siguiente afirmacin: "En C/C++ una cadena de caracteres (string) es un array de caracteres"

es claro en que sentido esta afirmacin es valida, una cadena de caracteres es un array y no una variable de tipo individual, como un entero o un float, sin embargo hay ligeras diferencias entre los dos conceptos, el de array y el de cadena de caracteres.. Mientras que un array de caracteres: es un conjunto ordenado de 'n' bytes de cualquier valor, una cadena de caracteres es solo un subconjunto de ese array, desde el primer char hasta el primer '\0' encontrado en el array. En realidad no es totalmente exacto decir 'subconjunto', pues si por error nuestro array de caracteres no tiene un '\0', las funciones standard lo buscaran fuera del array, y as la cadena de caracteres llegara a tener mayor extensin que el propio array, desbordando su capacidad. Luego de la declaracin-inicializacin siguiente: char cad[20] = "hola"; el largo de la cadena de caracteres es 4, valor que se puede obtener con el llamado "strlen(cad)", sin embargo el largo del array es 20, pues siguen siendo 20 los bytes en memoria asociados al nombre 'cad', este valor puede ser obtenido con: sizeof(cad); Este valor de limite de array no impide que se pueda sobrescribir mas all del mismo utilizando un indice que exceda el sizeof() del mismo (generalmente un grave error), para evitarlo se podra consultar ese valor numrico. Lamentablemente es un recurso que no esta disponible cuando se pasan arrays como argumentos a otra funcin, pues todo array es recibido por la funcin llamada a travs de un puntero a su elemento inicial. El hecho de que sizeof(cad) y strlen(cad) ofrezcan dos valores diferentes justifica el que se hable de diferencias entre los dos conceptos. A este respecto se pueden formular dos preguntas interesantes: Es posible que una cadena de caracteres tenga dos bytes con valor 0 ? Es posible que un array de caracteres tenga dos bytes con valor 0?

Si uno se atiene a las definicin estricta de cadena de caracteres la respuesta a la primera pregunta es 'NO', en una cadena de caracteres hay un solo byte (char) con valor 0, y coincide con su ultimo elemento.

Metodologa de la Programacin II

79

Tema 1 En cambio, segn creo, la segunda pregunta debe responderse afirmativamente, un array de caracteres no cambia de tamao durante la ejecucin de un programa, si lo hemos declarado de 20 char seguir siendo de 20 hasta el final, hay 20 bytes en memoria que le pertenecen solo a ese array. Y nada impide que dos o mas de esos bytes tengan el valor 0. La cuestin no es solo terica, supongamos que queremos elaborar un programa para analizar datos de ficheros binarios, por ejemplo de ficheros EXE, en tal caso nos encontraremos con bytes que valen 0 y estn en cualquier posicin, aqu no tiene sentido el pensar en esas colecciones de bytes como 'cadenas de caracteres', no son 'palabras' ni 'texto', sin embargo queremos hacer un programa que lea y almacene los bytes en un array, y construir funciones que analicen sintacticamente ese flujo de bytes. Algunas funciones standard de lectura de filas detienen cada lectura ante un '\n' o ante un '\0', pero hay otras que permiten leer conjuntos de bytes y los almacenan cualquiera sea su valor, y puesto que son bytes, y estos se corresponden con el tipo char, no tenemos otra opcin que almacenarlos en arrays de caracteres. Ahora bien, la mayora de las funciones de tratamiento de cadenas, como las de string.h, no nos sern tiles, pues interpretan el char 0 como corte de una cadena de caracteres, por lo tanto deberemos construir funciones alternativas. Teniendo en cuenta las diferencias enumeradas respecto a array y cadena de caracteres, podramos adoptar la siguiente definicin: -En C y C++, una cadena de caracteres es un array de caracteres terminado en '\0'. 1.3.8.2 Aseguramiento del fin de cadena Cuando declaramos un array o una variable cualquiera sin darle un valor inmediatamente, sin 'inicializarla', esa variable o array pueden contener cualquier valor, se dice que su valor es indeterminado, las variables globales son una excepcin pues son inicializadas con un valor default por el compilador, pero la mayor parte de los datos sern locales a una funcin (sea main() o cualquier otra) y por lo tanto no sern inicializados automticamente. Supongamos ahora las siguientes lneas de cdigo: int main () { int largo; char cad[5]; largo = strlen(cad); ........................etc. La pregunta es "cual es el valor de la variable 'largo'?", y el error es creer que ese valor deba ser necesariamente 5, de hecho el valor de 'largo' podra ser 0, 1, 9932, 234, o casualmente 5. Porque razn?, con la declaracin de 'cad' hemos declarado un array de caracteres, reservando 5 bytes de memoria esttica para ese array, pero no hemos inicializado ningn dato, lo mas probable es que esa regin de memoria conserve datos aleatorios de algn programa anterior, el valor que nos dar "strlen(cad)" se basara en haber comenzado a contar caracteres en memoria hasta encontrar el '\0', que podra estar en cualquier sitio, si se encontr en el primer byte inspeccionado el valor de 'largo' Serra 0. 80 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ Si sacamos en pantalla el contenido de esa cadena, sea con printf ("%s", cad) de C, o con cout<<cad de C++, su contenido Serra totalmente arbitrario y muy probablemente veamos caracteres 'extraos', esto ocurrir porque las funciones de impresin en pantalla tambin confan en el char 0 para determinar el fin de cadena. Los dos siguientes grficos muestran un contenido hipottico de los bytes de memoria asociados a la variable 'cad', son los primeros cinco bytes representados. En el primero se muestra un estado posible luego del cdigo anterior, los primeros cinco bytes estn reservados para el array de caracteres 'cad', pero en memoria esos bytes contienen un valor aleatorio e independiente, en este caso si se llamara a la funcin strlen(cad) retornara el entero 15 (cuenta 15 antes de encontrar el 00), y si se sacara en pantalla con "printf" o "cout" se veran los caracteres de la izquierda (subrayados con rojo).

En cambio, si al declarar el array lo inicializamos, char cad[5]="hola"; o bien en un primer paso lo declaramos y luego copiamos "hola" en la cadena con strcpy() char cad[5]; strcpy(cad, "hola"); en ambos casos estar asegurada la presencia del '\0' que indica el fin de cadena.

Ahora strlen() nos indicara que el largo de cadena es 4, y las funciones de impresin en pantalla funcionaran normalmente, todo gracias a la presencia del char '\0'. La idea es muy simple, sin embargo es necesario cometer muchos errores y desarrollar mucha practica con cadenas 'tipo C' antes de sentirse cmodo con ellas. Las libreras "string.h" y "mem.h" contienen muchas rutinas para tratar con cadenas de caracteres, es necesario conocer en detalle el modo en que cada funcin trata la cuestin del '\0' final para no encontrarse con sorpresas. 1.3.8.3 Ejemplos de funciones standard Como ejemplo observaremos el modo de operar de "strset" y "memset" y las precauciones necesarias en relacin al valor '\0'. Memset (char *cad, int ch, int n); Esta funcin setea los primeros 'n' char de una cadena al valor pasado como segundo argumento Metodologa de la Programacin II 81

Tema 1 (ch). Si queremos setear los primeros 5 bytes de una cadena con espacios se podra llamar a la funcin del siguiente modo: memset (cad, ' ', 5); La operacin es muy simple y aparentemente inofensiva, sin embargo !nada relacionado con cadenas en C/C++ es simple!. Esta funcin no hace nada en relacin al '\0', ni lo usa ni lo setea, podra ocurrir que antes de la anterior lnea de cdigo el array 'cad' tuviera la cadena "uno", con el cero en su cuarto byte, en tal caso el llamado a memset estara sobrescribiendo ese cero y el limite pasara a ser indeterminado. Si nuestro propsito es que la cadena contenga solo esos cinco bytes seseados es muy importante que, luego de haber invocado a memset, nos aseguremos del fin de cadena, por ejemplo con: cad[5]='\0'; //Tambin es posible "cad[5]=0", pues el 0 (entero) //se convierte implcitamente en 0 (char)

podra ser el caso de que tuviramos muy claro que el fin de cadena esta mas all de esos 5 bytes y lo quisiramos conservar, pero si solo nos interesa una cadena con el carcter ascii 'ch' repetido 'n' veces, una buena practica Serra asegurarnos el fin de cadena con "cad[n]=0", para evitar problemas posteriores. Strset (char *cad, int ch); La explicacin de ayuda en lnea de Borland dice que strset "setea todos los caracteres de una cadena a 'ch' ". Esto significa que si queremos que 'toda' nuestra cadena 's' pase a estar compuesta por asteriscos (ascii=42) podemos escribir: strset (cad, '*'); // es equivalente "strset (cad, 42)"

La diferencia con memset() es bastante clara, aquella era para setear 'n' caracteres y esta para 'toda' la cadena, y may esta el principal peligro, en la palabra 'toda'. Pues esta fusin confa exclusivamente en el '\0' para determinar el fin de la cadena que seteara. Supongamos un programa que comenzara con las siguientes lneas: int main () { char cad[20]; strset (cad, '*'); .....................etc. Estas lneas de cdigo son muy peligrosas, todo depende de si la funcin encuentra o no un '\0' antes de llenar de asteriscos todo lo que encuentre, hasta llegar al fin del segmento, donde sobrescribira los valores de stack para salir bien de main() y posiblemente habra que resetear la maquina. La explicacin de la funcin dada por la ayuda de TurboC++ es demasiado escueta y no menciona este tipo de problemas, la enunciacin es correcta: "setea toda la cadena" con el carcter indicado, pero es fcil olvidar que significa esto en los lenguajes C/C++, significa "todo lo que encuentre hasta dar con un '\0' " 82 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ podra darse una lista de posibles errores para los usos de cada una de las funciones que involucran a cadenas. Todos estn relacionados con el problema del limite del array de caracteres. Se mencionan a continuacin algunos de los muchos errores posibles. 1.3.8.4 sobre escritura de variables Ya se vio un ejemplo con strset() de como es posible exceder los bytes que corresponden a un array de caracteres y sobrescribir bytes que no le corresponden a esa variable. Veamos un ejemplo simple que involucra a la funcin strcat. La funcin strcat(cad1, cad2) concatena dos arrays de caracteres 'pegando' el segundo argumento luego del primero. int main () { char cad1[] = "hola"; char cad2[] = " mundo"; strcat (cad1,cad2); ....................etc Lamentablemente estas lneas sern problemticas, el modo en que se declararon los arrays hace que para 'cad1' haya cinco bytes en memoria ("hola" mas el '\0') y 6 bytes para 'cad2'. El resultado esperable seria el de que 'cad1' tuviera ahora la cadena "hola mundo", pero esto implica 10 bytes mas el '\0', lo cual desborda la capacidad de cad1. La funcin strcat() har lo que se le pide de todos modos, sobrescribiendo de ese modo bytes que pertenecen a otras variables. Muchas veces el problema no aparece inmediatamente sino cuando se intenta operar con las variables 'pisadas', en todo caso hay que tener en claro que es necesario reservar los suficientes bytes de memoria al usar una variable, ya se trate de una reserva esttica o dinmica. 1.3.8.5 Distintos sntomas de sobre escritura de variables Los efectos de sobrescribir una variable son muy variados, dependiendo en parte del tipo de dato sobrescrito y de la operacin involucrada. Algunos ejemplos sacados de la practica: 1. El corte imprevisto, durante el transcurso de un programa, como un flujo de archivo puede ser provocado por la sobre escritura del dato 'fstream' o el puntero "FILE*". 2. La aparicin de caracteres ascii 'extraos' casi siempre se debe a la supresin de un '\0' de fin de cadena. Si se escucha un pitido (beep) esto solo significa que entre los caracteres 'extraos' estaba el ascii =7. 3. sobrescribir el comienzo del segmento de datos provoca el mensaje "Null pointer assignment", este tema se tratara con mas detalle en el apartado dedicado a problemas tpicos con punteros. Son solo algunos ejemplos, los sntomas de sobre escritura de variables son tan variados como las mltiples posibilidades que producira dar un valor random, de modo no controlado, a uno o mas datos de nuestro programa.

Metodologa de la Programacin II

83

Tema 1 1.3.9 Modelos de memoria La memoria se puede representar como una coleccin de celdas contiguas con la capacidad de almacenar valores. Cada celda de memoria es individualizada por una 'direccin', que consta de un valor de segmento y otro de offset (desplazamiento dentro del segmento). Los detalles de como opera la cpu en relacin a la memoria dependen del tipo de procesador, si este esta funcionando en modo 'real' o 'protegido', sistema operativo y muchos otros factores. Cada segmento tiene una capacidad de 64 Kb. Una importante directiva en todos los programas es la que determina el MODELO DE MEMORIA que utilizara el programa al ejecutarse. El default suele ser el modelo 'small', pero existen varios modelos mas, sus principales diferencias estn en el modo en que utilizan los segmentos para almacenar cdigo, datos o ubicar la pila (stack). Al compilar y ejecutar un programa, en el IDE de TurboC++, podemos examinar los registros de la CPU para datos, cdigo y stack, estas son las siglas de tales registros: CS (code seg) Segmento de cdigo DS (date seg) Segmento de datos SS (stack seg) Segmento de pila El modelo de memoria utilizado por nuestro programa determinara cuanto espacio (en termino de segmentos) se usara para cdigo, datos y stack. El siguiente cuadro sintetiza las distintas opciones: Modelo memoria Tiny de Segmentos Comentarios cdigo, datos y stack utilizan un nico segmento, por lo tanto el cs = ds = ejecutable no ss podr ser mayor a 64 Kb. Es muy similar a un ejecutable con extensin .COM Un segmento para cdigo y uno para datos y stack. Es el modelo cs default ds = ss utilizado, a menos que se especifique uno diferente. cdigo usa mltiples segmentos, datos y pila comparten uno. Es cs el modelo ds = ss de eleccin si hay gran cantidad de cdigo y pocos datos Un segmento para cdigo y mltiples segmentos para datos y stack. cs Modelo apropiado cuando hay poco cdigo pero gran cantidad de ds = ss datos. Los datos son referenciados por punteros 'far' mltiples segmentos para cdigo y mltiples seg para cdigo y cs stack. Se usan ds = ss punteros 'far' para cdigo y para datos cs Similar a 'large' Dpl. Ing. Carlos Balderrama Vzquez

Small

Medium

Compact

Large Huge 84

Punteros en C/C++ ds = ss Flat cs ds = ss Usa punteros 'near' como el modelo 'small', pero hecho a medida para sistemas operativos de 32 bits

Estas categoras no son especificas de un lenguaje de programacin, la mayora de los compiladores de los diferentes lenguajes permiten optar por estos diferentes modelos de memoria. Las primeras versiones TurboC++ admiten solo los primeros seis modelos de la tabla, a partir de TurboC++3.01 esta disponible el modelo 'Flat' tambin. Cuando escribimos cdigo para libreras, un importante y complejo punto en la implementacin es tener en cuenta que las funciones deben tener la flexibilidad necesaria para adaptarse a diferentes modelos de memoria al pasar por el linker. Es instructivo observar las declaraciones de las libreras standard y el modo en que resuelven este tema. El principal detalle es el uso default de punteros 'near', para los modelos de memoria mas restringidos, y punteros 'far' para los mas extensos. 1.3.9.1 Rol de la STACK (pila) La distincin entre cdigo y datos es bastante natural, la sintaxis de C (o Modula-2) obliga a declarar, en una funcin, primero todos los datos antes de ingresar cualquier operacin (cdigo). Pero la nocin de STACK tiene una correspondencia menos obvia con lo que observamos en un lenguaje de alto nivel, se trata de algo manejado de modo automtico por el compilador. A lo sumo aparecer en relacin a mensajes de error como 'Stack overflow' o 'Desborde de pila' (tambin 'volcado de pila', en Windows). En programacin de bajo nivel (ensamblador) podemos operar directamente sobre la misma. Ahora bien, que es la pila?. Es una zona de memoria (no diferente del resto de memoria) requerida por todo programa (la misma cpu lo requiere) para un uso especial. Su funcin, sintticamente, es la de servir para el intercambio dinmico de datos durante la ejecucin de un programa, principalmente para la reserva y liberacin de variables locales y paso de argumentos entre funciones. El espacio utilizado para uso de la pila variara segn el modelo de memoria que utilice nuestro programa. Cuando un programa utiliza el modelo de memoria SMALL usa un mismo segmento para cdigo y stack, 64 Kb entre ambos. Suponiendo que nuestro programa opera con tal modelo de memoria, en la mayora de los compiladores de BorlandC++, el segmento de datos/stack presentara el siguiente aspecto, luego de ingresar en la funcin main() de un programa cualquiera:

Metodologa de la Programacin II

85

Tema 1 El inicio del segmento(0x0000) contiene una cadena de Copyright de Borland que no debe ser sobrescrita (pues dara el mensaje "Null pointer assignment"), luego se ubican las variables globales y constantes. Los literales, sean 'de cadena' o 'numricos' son tratados como constantes y almacenados en la parte baja. Al final de la pila (desde 0xFFFF) se guardan datos fundamentales para una buena salida del programa, y debajo se extiende una zona usada para almacenar variables locales y datos pasados como parmetros, por lo tanto es la parte mas dinmica del segmento (en el grafico la parte en blanco). El espacio total del segmento es de 64 Kb, esto significa que el monto de datos que podemos pasar a una funcin Serra un poco menor pues hay espacio ocupado por otros elementos. Esta limitacin se puede sortear utilizando otro modelo de memoria, pero por ahora nos centraremos en nuestro ejemplo con modelo small. Cuando se guardan en la pila mas valores de los que caben se produce un 'stack overflow', un desborde de pila. Las funciones recursivas trabajan haciendo una copia de si mismas y guardndola en la pila, por esa causa es frecuente provocar desbordes de pila de ese modo. Hay muchos motivos para utilizar la pila del modo mas econmico posible, y los punteros cumplen una gran utilidad en este caso, por ej al pasar arrays, estructuras u objetos entre funciones a travs de una direccin (solo 2 bytes). Otros detalles en relacin a punteros. Todo puntero que este dentro de este segmento y apunte a otra direccin del mismo segmento Serra un puntero 'near', para apuntar a un segmento diferente deberemos (en modelo small) explicitar un puntero 'far'. Una cuestin interesante es la de si la memoria dinmica se almacena en este segmento o en algn otro. Los detalles en la implementacin de memoria dinmica son en general bastante oscuros y dependen mucho del compilador utilizado, pero si el espacio reservado con 'new' se asocia a un puntero 'near' es claro que la memoria reservada estar dentro de este mismo segmento. Para estudiar este aspecto es recomendable ejecutar el programa en modo debugger y consultar los datos del puntero, el valor de segmento donde se encuentra y el valor de segmento adonde apunta. 1.3.9.2 Gestin de memoria en C++ Todas las variables, arrays, punteros y objetos en general tienen una duracin determinada en el transcurso del programa. Tales objetos son 'creados' y 'destruidos', o en otros trminos: se asocian sus nombres (identificadores) a una zona de memoria en la cual no puede asentarse otro objeto, y tales zonas de memoria son liberadas para el uso de otros objetos. 86 Dpl. Ing. Carlos Balderrama Vzquez

Punteros en C/C++ La existencia de tales objetos esta determinada segn tres formas bsicas de usar la memoria en C++. 1-Memoria esttica Los objetos son creados al comenzar el programa y destruidos solo al finalizar el mismo. Mantienen la misma localizacin en memoria durante todo el transcurso del programa. Estos objetos son almacenados (en compiladores Borland) al principio del segmento de datos. Los objetos administrados de este modo son: variables globales, variables estticas de funciones, miembros static de clases, y literales de cualquier tipo. 2- Memoria automtica Los objetos son creados al entrar en el bloque en que estn declarados, y se destruyen al salir del bloque. Se trata de un proceso dinmico pero manejado de modo automtico por el compilador (no confundir con memoria dinmica). Tales objetos se almacenan en la parte alta de la pila al entrar en la funcin o bloque. Este procedimiento se aplica a: variables locales y argumentos de funcin. 3-Memoria dinmica En este caso tanto la creacin como destruccin de los objetos esta en manos del programador, a travs de los operadores 'new' y 'delete'. El sitio donde se almacenan tales objetos se suele denominar en ingles 'heap' o 'free store', traducido como 'montculo' o 'memoria libre'. Pero el sitio preciso donde se encuentre tal 'montculo' depende del compilador y el tipo de puntero utilizado en la reserva de memoria dinmica. Cualquier tipo de objeto puede ser creado y destruido a travs de este procedimiento. En C y C++ la administracin explicita de memoria por parte del programador juega un rol muy importante, no es as en otros lenguajes (Basic, Smalltalk, Perl) donde la gestin principal es automtica. La administracin 'manual' permite un mayor grado de flexibilidad pero tambin multiplica la posibilidad de errores. Un modo de gestionar memoria dinmica en C++, aprovechando las ventajas de la memoria automtica, es la implementacin de destructores que sean llamados de modo automtico al salir de un bloque, y que se encarguen de la liberacin de memoria dinmica. 1.3.9.3 'R-value' y 'L-value' La forma mas simple de almacenar un valor en una direccin de memoria es a travs de asignaciones. A veces, ante una asignacin fallida, aparece el mensaje "Lvalue required", veamos que significa. Las expresiones "R-value" y "L-value" ("rigth-value" y "left-value"), originalmente significaban algo que puede estar a la derecha o a la izquierda de una asignacin. Por ejemplo: int x; 4 = x;

//Error en tiempo de compilacin: "Lvalue required" Metodologa de la Programacin II 87

Tema 1 El compilador nos informa de que se requiere un 'l-value' a la izquierda de la asignacin. El '4' es un literal numrico, y los literales son tratados como valores constantes, no pueden estar en esa posicin. Ahora bien, como definir "l-value"?. T. Jensen lo define como: "valor de la direccin de una variable", y Stroustrup como: "algo que esta en la memoria, que ocupa una regin continua de memoria". Esto ultimo se ilustra con las siguientes lneas: int a, b=4, c=5; a = b + c; el valor de 'a' Serra igual a 9, este valor 9 es producto de "a+b", no ocupa una zona de memoria, no hay un 'nombre' de variable (de una variable) asociado a el, es un valor temporal sin identificador propio. Por lo tanto, al no estar en memoria bajo un identificador, no es un "lvalue", por esa razn: b + c = a; //Error. "Lvalue required"

nos da ese mensaje de error. A pesar de la definicin de 'lvalue', como aquello "que puede estar a la izquierda de la asignacin", hay cosas donde esto no se cumple. Una variable declarada como constante es un 'lvalue', es un objeto en memoria, pero no puede estar a la izquierda en una asignacin.

88

Dpl. Ing. Carlos Balderrama Vzquez

Você também pode gostar