Nociones elementales

Que es una variable?

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

int var;

produce una asociacion entre el nombre 'var' y un espacio de almacenamiento en memoria. Por lo tanto hay dos elementos relacionados con el nombre 'var': un valor que se puede almacenar alli y una direccion de memoria para la variable, algunos autores se refieren a estos dos aspectos como el "rvalue" y "lvalue" de la variable.
Ademas 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,

1-Un byte es la menor unidad de informacion que pueden direccionar la mayoria de las computadoras. En la mayoria de las arquitecturas el tipo char ocupa un solo byte, por lo tanto es la unidad minima. Un bool admite solo dos valores diferentes, pero es almacenado como un byte. El tipo integer ocupa generalmente 2 bytes, un long 4, double 8, y asi con el resto de los tipos.
2-El otro punto es la relacion entre LO QUE HAY en una celda de memoria y COMO ES INTERPRETADO. Lo que hay en un celda cuya extension 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, podria 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 asociacion). 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 ninguna localidad de memoria hay algo como la letra 'a', lo que encontramos son valores binarios que en caso de estar asociados a char y en caso de que lo saquemos en pantalla como char hara que veamos encendidos ciertos pixeles de pantalla, en los cuales reconoceremos una representacion de la letra 'a'.

La representacion binaria de datos ocupa demasiado espacio, por ese motivo es preferible utilizar el sistema hexadecimal, ademas de ser muy facil de traducir a binario es mas economico que este o el decimal. Observar los bytes de un sector de memoria de un programa facilita la comprension sobre el modo en que cada tipo (type) se asocia a direcciones de memoria.
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 representacion de estos datos en memoria, en el segmento de datos, tal como lo muestra un debugger, tendra el siguiente aspecto (se omiten caracteres problematicos para navegadores y dejamos constancia que diferentes compiladores pueden ordenar los datos de otro modo):

ffd0    ........................       20 20 20 20 00 8F 12 00 00 00 F6 FF BC 04 00 FF
ffe0    ............6abcd..       F6 F6 00 00 F6 FF C7 04 06 36 61 62 63 64 00 00
fff0    .......................        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 codigo (int a) figuran al final, los bytes 05 00  son la representacion 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.
Ademas 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 asi porque esos valores (como 0B 01) guardan valores de STACK para restablecer algunos registros cuando el programa salga de main() y termine. Sobreescribir ese valor podria producir un crash.
2- Que la variable entera de valor 5 guarda este valor ubicando los bytes al reves. Lo logico seria que la representacion fuera 00 05, pero los bytes estan invertidos, esto es una norma general de la mayoria de los procesadores y responde a una pauta de mayor eficiencia en la lectura de variables numericas.
3- El array 'cad' se declara de modo implicto con 5 bytes, las cuatro letras mas el caracter terminador '\0', se ocupa un byte mas porque un numero par de bytes es mas eficiente. Observese que un array no invierte la posicion de sus elementos.
4- Un char ocupa exactamente un byte. El primer char esta definido con el caracter '6' que corresponde al ascii 0x36, la segunda variable char (hh) es seteada a partir de un valor entero (6) lo que genera una conversion implicita de tipos.

Se podria profundizar mas el tema de que funcion tiene este sector de memoria, su relacion con la pila (STACK) y los modelos de memoria, pero eso se vera en otros apartados. Por ahora es importante tener en cuenta la relacion entre el tipo (type) usado para declarar una variable y el modo en que se almacena en memoria. En la siguiente tabla se encuentran mas ejemplos:

DECLARACION Inicializacion Representacion en memoria Numero de bytes
int a; a = 5; 05  00    2
char ch; ch = 'e'; 65    1
char cad[]="hola";  - 68 6F 6C 61 00    5
long a; a = 4 04 00 00 00    4
long a; a=0x1234 34 12 00  00    4
long a; a = 65535 ff  ff  00  00    4

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 asociacion entre localidades de memoria y variable no siempre existe desde el comienzo al final de un programa. Las variables declaradas como 'locales' a una funcion solo tienen asociada una localidad de memoria mientras el flujo del programa se encuentra en tal funcion, al salir de la misma tales localidades seran usadas por otros datos. En cambio las variables 'globales' o declaradas como 'static' conservan su localidad de memoria durante toda la ejecucion del programa.

Que es un array?

Un array es una coleccion ordenada de elementos del mismo tipo (type), estos tipos pueden ser los que proporciona el lenguaje, como char, integer, float, long integer, etc., o bien puede tratarse de un tipo definido por el programador, como una estructura o una clase.
Estos elementos se encuentran ordenados en celdas consecutivas de memoria. Veamos los siguientes ejemplos:

Declaracion e inicializacion Representacion en memoria Bytes
int a []= {3, 345, 54, 4}; 03 00   63 01  72 01   03 27  2 x 4 = 8
int a[4]={2}; 02 00   00 00  00 00   00 00 2 x 4 = 8
char a [] = {"Mensaje 1"}; 4d 65 6e 73 61 6a 65 20 31   00 9+1= 10
char a [8] = {hola}; 68 6F 6C 61 00 00 00 00 7+1 = 8
long a [] = {9, 16, 0x23b2a}; 09 00 00 00  12 00 00 00 2a 3b 02 00 3 x 4 = 12

El tipo (type) del array determina cuantos bytes ocupa cada uno de sus elementos, y tambien de que modo se almacena el dato.
Es importante mencionar que este modo de inicializacion es solo posible cuando se realiza en la misma linea que en la declaracion, no es posible inicializar al mismo tiempo varios elementos de un array si lo hacemos en una linea diferente a la de la declaracion. Tambien hay que mencionar el hecho de que si damos mas elementos inicializadores que los que figuran entre corchetes se genera un error de compilacion, si damos menos elementos el compilador setea el resto de los elementos con el valor '0'.
Una cadena en C/C++ es representada internamente como un array de tipo char y utiliza un caracter 'terminador' para indicar el fin de la cadena, ese caracter es el correspodiente al Ascii = 0. Para mas detalles ver cadenas estilo C/C++ .

La notacion  "Nombre_array[int n]" nos permite seleccionar cada uno de los elementos de ese array, esa expresion tiene el mismo tipo (type) que un elemento individual, y esto puede ser importante para distinguir mas claramente las asignaciones y conversiones posibles.

Que es un puntero?

Un puntero es un tipo especial de variable, que almacena el valor de una direccion de memoria, esta direccion puede ser la de una variable individual, pero mas frecuentemente sera la de un elemento de un array, una estructura u objeto de una clase. Los punteros, al igual que una variable comun, 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 tamaño (sizeof) del objeto apuntado, el valor almacenado por el puntero sera el de una unica direccion de memoria. En sentido estricto un puntero no puede almacenar la direccion de memoria de 'un array' (completo), sino la de un elemento de un array, y por este motivo no existen diferencias sintacticas entre punteros a elementos individuales y punteros a arrays. La declaracion 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, asi 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 direccion de otro segmento)

Veamos los efectos de un codigo como el siguiente, en la zona de almancenamiento de datos:

char cad[] = "hola";
char * p;
p = cad;           //Puntero 'p' apunta a 'cad'

El puntero esta en la direccion 0xffee pero el valor que hay en esa localidad de memoria es otra direccion, 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 lineas de codigo no hemos indicado a que caracter del array apunta el puntero, pero esa notacion es equivalente a:

p = &cad[0];

que indica de modo mas explicito que se trata de la direccion del primer elemento de ese array de caracteres. El juego con las direcciones puede ilustrarse tambien del siguiente modo:

ffee     F0      <----- El puntero ocupa dos bytes para representar la direccion FFF0, direccion a la que 'apunta'.
ffef     FF      <-----

fff0     61     <------ cad[0]. .Primer char del array de caracteres, direccion apuntada por el puntero
fff1     61     <------ cad[1]
fff2     61    <------ cad[2]  
fff3     61    <------ cad[3]
fff4      0   <------ cad[4]   Fin del array, caracter ascii = 0 de fin de cadena

Puesto que un puntero tiene como valor una direccion de memoria, es logico que al llamar a funciones de impresion con un puntero como argumento, la salida en pantalla sea la de una direccion de memoria. Para este tipo de pruebas es interesante usar la libreria iostream.h de C++, pues no obliga a especificar el formato (como hace printf ). Para un puntero 'p' la salida en pantalla sera 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 direccion de memoria, la cual es FFF0. Porque razon la impresion con 'cout' nos da 4 bytes? Porque agrega 2 bytes (8f y 82) para indicar el 'segmento' donde se encuentra esa direccion. Se trata en todo caso de una misma localidad de memoria, con distinto formato de presentacion 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 direccion de memoria, sino un conjunto de caracteres hasta encontrar el '\0'.

Un puntero puede almacenar la direccion de ("apuntar a") muy diferentes entidades: una variable, un objeto, una funcion, un miembro de clase, otro puntero, o un array de cada uno de estos tipos de elementos, tambien puede contener un valor que indique que no apunta actualmente a ningun objeto (puntero nulo).

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 declaracion de un tipo no produce ningun efecto en memoria, no hay ningun identificador donde almacenar un dato, por esa razon no tendria sentido, dentro de la definicion de una estructura o clase , intentar dar un valor a sus datos, seria lo mismo que intentar dar un valor a un tipo predefinido, por ejemplo:
long = 8;
Para asignar un valor necesitamos un objeto, pues un objeto implica una region de memoria donde almacenar un valor.

El almacenamiento en memoria de una union, enumeracion 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 tambien de funciones, y que por ello presenta novedades de importancia respecto a los tipos anteriores.

Clases

Una clase es basicamente un agregado de datos y funciones para manipular esos datos. Las clases, y la programacion 'orientada a objetos' en general, ha representado un gran avance para produccion de software a gran escala, los recursos de herencia, encapsulamiento, ocultacion de datos, clases virtuales, etc., estan pensados con esa finalidad. Aqui solo nos detendremos en la nocion minima 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 funcion. Una vez declarada la clase podemos definir objetos como pertenecientes a ese tipo. Una clase no ocupa espacio, pero si un objeto perteneciente a esa clase. El espacio ocupado en memoria por tal objeto puede ser conocido a traves de 'sizeof'.
gente pp1;
cout<<sizeof(pp1);       //saca en pantalla '12'
El valor podria ser ligeramente diferente segun el compilador, por efecto de optimizacion. 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 funcion.
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 funcion (aqui 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 version) posando el cursor sobre el objeto que nos interesa (aqui 'pp1' y 'pp2') y pulsando 'Alt+f4', tambien 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 lineas de codigo previas, se puede observar lo siguiente:

El valor especifico de cada dato (como el valor de segmento) puede variar con cada ejecucion, lo que cuenta es la relacion entre tales valores. Interpretemos estos datos.

1- En la ventana de cada objeto (pp1 y pp2) figura en primer lugar la direccion 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 estan almacenados alli donde indica el offset, no en otro sitio, por lo tanto un puntero al objeto 'pp1' apuntara (en este caso) a la misma direccion 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 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 linea, se encuentra un espacio donde se enumeran las funciones miembro de la clase. Alli encontramos el prototipo de la funcion miembro y al lado la direccion de memoria donde se inicia su codigo. Ese es el valor que almacenaria un puntero a dicha funcion. Observese que tal direccion es la misma para ambos objetos, por la razon antes mencionada de que hay solo una copia de funciones miembro por objeto. El segmento donde se encuentra tal funcion se corresponde con el valor que muestra la ventana CPU para CS (segmento de codigo).

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 ningun valor.
-Un objeto de tal clase si define una region 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 sucesion consecutiva, mientras que un puntero a objeto sera (como todo puntero) un par de bytes que apunte a una direccion de memoria donde se almacena el objeto.

PRINCIPAL




Free Web Hosting