Story Transcript
Facultad de Ciencias Exactas, Ingeniería y Agrimensura Departamento de Sistemas e Informática Escuela de Electrónica Informática II – 1er Año.
Punteros & Estructuras
1. Conceptos de memoria. Cuando se habla de memoria en el contexto de un programa, se trata de una serie de celdas que tienen la capacidad de almacenar datos durante la ejecución del programa. Estas celdas se denominan ‘byte’ o antiguamente ‘word’ representados en 8 bits. Que es la unidad básica de manejo de información en sistemas. Estas celdas tienen características básicas: Su dirección. Es el lugar donde el procesador encontrará esta celda. Esta dirección será un valor numérico que es único para cada celda. Las direcciones de las celdas se escriben por convención en formato hexadecimal. (Ej.: X03C4782). El valor almacenado. Cada celda tiene un tamaño de 8 bits, que permite un rango de 256 valores (de 0 a 255 / x00 a xFF / 00000000 a 11111111). Su organización. Toda la memoria se organiza en forma secuencial (consecutivo), que significa que las celdas tienen una dirección que va incrementándose hasta el final físico de la misma.
Las computadoras actuales estan organizadas de acuerdo a la arquitectura Von Neuman
Dependiendo de las características de cada máquina, las direcciones de la memoria se organizan dentro de rango específicos de valores que luego cada sistema administra de acuerdo al contexto del hardware y software de base. Cuando se trabaja con máquinas con un sistema operativo subyacente, la administración de la memoria es propiedad exclusiva del sistema con el cual interactúa un programa. En el caso de programas que operan directamente sobre hardware, la administración es responsabilidad del propio programa. El programa solicita al sistema porciones de memoria para su uso interno y lleva un control sobre los bloques de memoria libres y en uso.
Informática II – Punteros & Estructuras
Pág. 1
Facultad de Ciencias Exactas, Ingeniería y Agrimensura Departamento de Sistemas e Informática Escuela de Electrónica Informática II – 1er Año.
Punteros & Estructuras
2. Variables: Tipos, tamaños y alojamiento. En los lenguajes de programación se definen variables, pero que son las variables? Son entidades que tienen un nombre, es de un tipo, un valor y un lugar donde se almacena. En la declaración de una variable se definen tres cosas: Nombre. Que tiene sus convenciones. En C una variable debe comenzar con una letra y luego tener la secuencia de letras y números hasta un máximo de longitud. Lugar de alojamiento. Porción de memoria que se reserva a esa variable. La variable tiene asociada la dirección de memoria donde comienza el alojamiento de los datos, cuantos bytes consecutivos se asignaron depende del tipo de variable. Para obtener la dirección asignada a una variable se utiliza un operador &. Entonces la dirección de cualquier variable se obtiene haciendo &variable. Tipo. Propios del lenguaje.(Ej. int, float,etc). El tipo define dos cosas muy importantes: La longitud en byte que necesita (para el rango de valores que define), y la forma binaria en que se almacenan los valores asignados a la variable.
En un instrucción de asignación, (int n= 8), el sistema toma su dirección donde almacenar y el tipo para definir el formato en bytes del dato a guardar. Es decir cuando la variable esta a la izquierda del igual, podemos decir que indica donde y como guardar. Cuando se usan variables dentro de una expresión, como un argumento de función, etc. (n!=6), aquí decimos en general que nos referimos al valor, pese a que el sistema buscará la dirección donde está, su tipo y luego interpreta para obtener el valor. Esto nos acerca a la idea de que la referencia a una variable dentro de una expresión, siempre hace mención al dato guardado y nos oculta la problemática de donde y como estan guardados los datos. Esto es una facilidad enorme para muchos usos dentro de la programación. Pero esto nos reduce las posibilidades de nuestros programas a cosas sencillas, ninguna posibilidad de controlar donde y como almacenar. Tampoco puedo definir datos complejos. A partir de este razonamiento es que surgen el concepto de punteros, que en el lenguaje C y luego el C++, tienen un papel determinante en la potencia del lenguaje. Resumen: a partir de una declaración del tipo, “ float area; “ queda definido: Nombre “area”. Tipo de dato “float”. Forma de almacenar: punto flotante de 32 bits. Comienza su almacenamiento en la dirección “&area”.
Informática II – Punteros & Estructuras
Pág. 2
Facultad de Ciencias Exactas, Ingeniería y Agrimensura Departamento de Sistemas e Informática Escuela de Electrónica Informática II – 1er Año.
Punteros & Estructuras
3. Los punteros. Los punteros se definen como un tipo de variable que almacena direcciones. Como toda variable tiene un nombre, es de un tipo, un valor y una dirección donde se almacena. Las características de las variables puntero, es que estan vinculadas a los tipos de datos primitivos del lenguaje o a tipos ya conocido o declarados en el programa. Si bien todos los punteros almacenan direcciones, por lo tanto su longitud (tamaño del dato que se almacena) es la misma, toman algunas características del tipo de dato del que provienen. Puedo definir punteros asociados a variables, a vectores (array) o a estructuras más complejas, como así tambien punteros universales. Para declarar un puntero, se debe especificar a que tipo de datos apuntara la dirección que contiene. La forma de declarar un puntero es: tipo * nombre;
Ejemplo: “float * nro;”
Esta variable puede almacenar un valor entero positivo, que representa una dirección, no almacena un número flotante, indica que en esta dirección (valor de la variable nro) comienza el almacenamiento de un número del tipo float y por lo tanto desde esa dirección serán cuatro 4 bytes. Para obtener el valor del flotante almacenado en la dirección que dice el puntero “nro”, se utiliza un operador de indirección *. Ejemplo: int n; declara una variable entera (32 bits / 4 bytes). int *j; declara un puntero tipo entero (almacena direcciones 32 bits / 4 bytes). j contiene una dirección donde comienza un entero. *j valor entero almacenado en la dirección apuntada por “j” (indirección). n contenido=“valor del entero” j contenido=“valor de la dirección”. &n dirección del entero en “n” *j contenido en la dirección de “j”. j = &n Almaceno en j la dirección de n. *j Representa el valor entero almacenado en n.
Informática II – Punteros & Estructuras
Pág. 3
Facultad de Ciencias Exactas, Ingeniería y Agrimensura Departamento de Sistemas e Informática Escuela de Electrónica Informática II – 1er Año.
Punteros & Estructuras
Representamos “int n=40, *j=&n;” suponemos que se almacenan: n en la dirección 0x0013FF60 y j en la dirección 0x0013FF48
Lugar: 4 byte donde esta almacenado j
Valor j 0x0013FF60
Dirección de j: &j 0x0013FF48
Lugar: 4 byte donde esta almacenado n
Valor *j=n 40 = 0x00000028
Dirección de n:&n j 0x0013FF60
4. Administración dinámica de memoria. Hasta ahora vimos definición de variables y punteros tomando direcciones de las variables ya definidas. Esto significa que lo definido es lo que puedo usar. El uso de punteros me permite administrar datos de longitud variable, como ser los array de tamaño dinámico. En el desarrollo de un programa, el array puede variar de tamaño por distintas situaciones. Como ejemplo simple si tenemos un archivo de texto que se necesita cargar a memoria. Al hacer el programa no se conoce cuantas líneas tiene y no se conoce el largo de cada línea. La solución es poder administrar la memoria de los datos en forma dinámica, que en este caso del ejemplo es poder hacer los array del tamaño que necesita al leer una línea. Es una modalidad diferente, que le da sentido al uso de punteros. Cuando se declara un puntero, se reserva memoria para almacenar el valor que maneja: una dirección, por lo tanto en las máquinas de 32 bits, el tamaño será de 4 bytes. Pero no reserva memoria para lo que apunta, y debe darse valor antes de usarse, para lo cual se utilizan funciones específicas. Para usar la memoria en forma dinámica en necesario recurrir a punteros, el cual apunta el inicio de la memoria reservada. Existen funciones especiales para solicitar al sistema bloques de memoria del tamaño que se necesite. Por ejemplo, si necesitamos un array de enteros, declaramos un puntero a enteros, (int *pint) que asigna memoria para la variable puntero (32 bits/4bytes), pero no memoria para el array, hay que obtenerla. Cuando se obtiene asigna al puntero la dirección del primer elemento del array. Aquí aparece un segundo objeto que es generado en forma dinámica y se accede a través del puntero. En este caso el puntero apunta a una zona de memoria reservada en forma dinámica. Esta memoria luego puede liberarse y asignar al puntero otro sector de diferente tamaño de acuerdo a las necesidades. La obtención de memoria en C++ se realiza con el operador new y su liberación con delete.
Informática II – Punteros & Estructuras
Pág. 4
Facultad de Ciencias Exactas, Ingeniería y Agrimensura Departamento de Sistemas e Informática Escuela de Electrónica Informática II – 1er Año.
Punteros & Estructuras
También podemos usar las llamadas de que provee C: void *malloc( size ); //llamada que devuelve puntero a un bloque de size bytes de tamaño. void *calloc(nmemb , size); //llamada que devielve puntero a un bloque de nmemb mienbros de size tamaño. void *free(void *ptr); //Libera un bloque otorgando por malloc/calloc.
Ejemplos: acción requerida
Asignación
Liberación
Un elemento del tipo 'float'
float * pf = new float;
delete pf;
Array de '40' elementos de tipo 'float'
float * pf = new float[40];
delete [] pf;
Con el operador new obtenemos bloques de memoria según se necesite, asignando al puntero el valor de comienzo de la zona. Cuando se quiere liberar esa memoria usamos delete. Cuando se solicita cierta cantidad de memoria, puede pasar que no tenga disponible. El operador en ese caso devuelve un valor NULL, (cero binario). Por lo tanto es necesario tener en cuenta que en programas que requieren grandes cantidades de memoria puede fallar la asignación y es necesario verificar que el resultado del pedido no sea NULL. En el caso del ejemplo del array, el puntero tiene asignada la dirección del primer elemento, para acceder al segundo se debe incrementar el valor del puntero lo suficiente como para que apunte al siguiente elemento float. El puntero fue definido del tipo float, eso define el tamaño de lo que apunta, por lo tanto al incrementar (pf++) automáticamente avaza los 4 bytes que necesita para apuntar el segundo elemento.
5. Aritmética de punteros. La posibilidad de operar con punteros se ve facilitada por su propia algebra. Las operaciones matemáticas con punteros son ligeramente modificadas para darle un efecto que facilite los accesos a datos dentro de los bloques de memoria. Si es un apuntador a enteros (int *pi), cuando se incremente se quiere que avance hasta el próximo entero, por lo tanto deberá subir 4 posiciones de memoria que es el tamaño del un entero (int). De aquí que el incremento (decremento) de las variables punteros dependen del tipo de dato que apuntan. Este criterio es el usado en todas las operaciones básicas que pueden realizarse con variables punteros. La unidad de operación es el tamaño del tipo de datos que lo define. Podríamos decir entonces que si el puntero: • Apuntador de char, su unidad de incremento/decremento será de 1 byte. • Apuntador de short, su unidad de incremento/decremento será de 2 bytes. • Apuntador de int, su unidad de incremento/decremento será de 4 bytes. • Apuntador de double, su unidad de incremento/decremento será de 8 bytes. Si se extiende esta lógica a todas las operaciones, el resultado de una operación tendrá en cuenta el tipo. Las direcciones de memorias pueden considerarse números naturales, las operaciones básicas contempladas (+,-,*,/) deben devolver números naturales, para que apunten a una dirección válida.
Informática II – Punteros & Estructuras
Pág. 5
Facultad de Ciencias Exactas, Ingeniería y Agrimensura Departamento de Sistemas e Informática Escuela de Electrónica Informática II – 1er Año.
Punteros & Estructuras
Ejemplos: int *arr = new int[230]; ... cout