9.1 KiB
PEC 2
Volver a la página principal de "Fundamentos de la Programación"
Índice
4. Estructura de memoria
Conceptos básicos de los efectos que produce un programa en la memoria.
4.1. Memoria
Un programa no se ejecuta desde el disco, sino que se carga en la memoria RAM (Random Access Memory). La memoria es un recurso finito y limitado, por lo que es importante gestionarla de forma eficiente. Por este motivo, el SO se encarga de ir moviendo los programas entre la memoria y el disco, pero desde la vista del programador debemos entender que el programa siempre está cargado en memoria.
Cada posición está identificada unívocamente con un valor numérico (su dirección) y tiene capacidad de 8 bytes.
Cuando asignamos un valor (constante o variable) se asigna un rango de direcciones donde pondrá su valor.
4.1.1. Información y espacio
Tipo | Tipo C | Tamaño en bytes | Rango de valores |
---|---|---|---|
Entero | char | 1 | -128 to 127 o 0 to 255 |
Entero | unsigned char | 1 | 0 a 255 |
Entero | signed char | 1 | -128 a 127 |
Entero | int | 2 o 4 | -32,768 a 32,767 o -2,147,483,648 a 2,147,483,647 |
Entero | unsigned int | 2 o 4 | 0 a 65,535 o 0 a 4,294,967,295 |
Entero | short | 2 | -32,768 a 32,767 |
Entero | unsigned short | 2 | 0 a 65,535 |
Entero | long | 4 | -2,147,483,648 a 2,147,483,647 |
Entero | unsigned long | 4 | 0 a 4,294,967,295 |
Real | float | 4 | 1.2E-38 a 3.4E+38 -> 6 decimales |
Real | double | 8 | 2.3E-308 a 1.7E+308 -> 15 decimales |
Real | long double | 10 | 3.4E-4932 a 1.1E+4932 -> 19 decimales |
En los casos con dos opciones de tamaño es porque depende del sistema operativo y del compilador. En general, en sistemas de 32 bits, int
y long
son de 4 bytes, mientras que en sistemas de 64 bits, int
es de 4 bytes y long
es de 8 bytes.
Con la instrucción sizeof
se puede obtener el tamaño en bytes de un tipo de dato.
#include <stdio.h>
#include <float.h>
int main() {
printf("Storage size for float : %d \n", sizeof(float));
printf("Minimum float positive value: %E\n", FLT_MIN );
printf("Maximum float positive value: %E\n", FLT_MAX );
printf("Precision value: %d\n", FLT_DIG );
return 0;
}
4.2. Memoria estática
Cuando ejecutamos un programa, el sistema operativo busca una zona en la memoria donde este quepa. Por tanto, la dirección inicial de nuestro programa cambiará cada vez que lo ejecutemos.
args - Donde se guardan los argumentos de la línea de comandos. BSS - Donde se guardan variables globales y estáticas sin iniciarlizarse en la declaración. Valores a 0 DS (Data Segment) - Donde se guardan las variables globales y estáticas inicializadas en la declaración. text
Por ejemplo:
#include <stdio.h>
int a; // BSS
int b=2; // DS
int main() {
static char c; // BSS
const float d=3.14; // DS
return 0;
}
La memoria de BBS y DC se reserva en tiempo de compilación, con lo cual es fija y por eso se conoce como memoria estática.
4.3 Relación entre objetos y memoria
Cuando declaramos un objeto, ya sea variable o constante, el nombre que le damos queda vinculado con la dirección de la posición de memoria donde este comienza.
Para saber la dirección donde ha quedado guardada una variable, lo podemos hacer con el operador &. Por ejemplo:
#include <stdio.h>
long b;
int c;
int main() {
int a=0;
float d=5.0;
printf("Var %c (%d bytes) starts at %ld and ends at %ld\n", 'a', sizeof(a), (unsigned long)(&a), (unsigned long)
(&a)+(sizeof(a)-1));
printf("Var %c (%d bytes) starts at %ld and ends at %ld\n", 'b', sizeof(b), (unsigned long)(&b), (unsigned long)
(&b)+sizeof(b)-1);
printf("Var %c (%d bytes) starts at %ld and ends at %ld\n", 'c', sizeof(c), (unsigned long)(&c), (unsigned long)
(&c)+sizeof(c)-1);
printf("Var %c (%d bytes) starts at %ld and ends at %ld\n", 'd', sizeof(d), (unsigned long)(&d), (unsigned long)
(&d)+sizeof(d)-1);
return 0;
}
Por ejemplo, si queremos hacer la representación gráfica de la memoria para el siguiente programa:
#include <stdio.h>
long b;
int c;
int main() {
int a=5;
short b=7;
char c='a';
return 0;
}
Tendríamos un resultado similar al siguiente (los valores binarios que hay no son los correctos):
address | ... | X-1 | X-2 | X-3 | X-4 | X-5 | X-6 | X-7 | X-8 | ... |
---|---|---|---|---|---|---|---|---|---|---|
value | ... | 00000000 | 00000000 | 00000000 | 00000000 | 00000000 | 00000000 | 00000000 | ... | |
... | a | a | a | a | b | b | c |
4.4. El tipo puntero en C
Los punteros son variables que en vez de contener un valor (como los enteros, caracteres o reales), contienen una dirección de memoria.
Un puntero es una variable cuyo valor es una dirección de memoria. Dado que un puntero contiene una dirección de memoria, el espacio que ocupa en memoria será siempre el mismo, el tamaño de una dirección. Esto dependerá si el sistema es de 32 o 64 bits, pero será independiente del tipo que le asignemos al puntero.
4.4.1. Declaración
Al igual que cualquier otro variable, un puntero se declarará para poderlo utilizar:
#include <stdio.h>
int main() {
/* Variable definition */
type *p=NULL;
}
Donde type es cualquier tipo de datos (entero, real, etc.). La constante NULL tiene un valor 0, y se interpreta como que este puntero no ha sido inicializado, o sea, que no apunta a ningún sitio.
4.4.2. Operaciones
Aunque las direcciones son iguales sea cual sea el tipo, se le asigna un tipo para indicar qué contiene la dirección que se guarda en este puntero. Hay que tener mucho cuidado con los tipos de los punteros, aunque los punteros de todos los tipos ocupan lo mismo, el tipo asignado a un puntero tiene un gran efecto en el resultado que se obtiene al operar con él.
Dado que un puntero contiene una dirección de memoria, que es un valor numérico, podemos realizar las mismas operaciones que con cualquier valor numérico.
4.4.2.1. Asignación
Si queremos que un puntero apunte a una determinada variable, o sea, asignar al puntero la dirección de una determinada variable, lo haremos de la siguiente manera (fijaos en que los tipos deben coincidir entre variable y puntero):
#include <stdio.h>
int main() {
/* Variable definition */
int a=3;
float b=4.5;
int *pa=&a;
float *pb=&b;
}
En este ejemplo, a la variable pa
se le asigna la dirección de la variable a
, y a la variable pb
la dirección de la variable b
.
4.4.2.2. Contenido
La dirección de una variable es una información que por sí sola no nos aporta gran cosa, ya que cada vez que ejecutamos nuestro programa este valor cambiará. Lo realmente importante es el hecho de que a partir de esta dirección podemos acceder al contenido que se guarda en esa posición de memoria.
El siguiente código muestra el operador contenido, que nos permite recuperar el contenido a partir del puntero.
#include <stdio.h>
int main() {
/* Variable definition */
int a=5;
int *pa=NULL;
pa=&a;
*pa=25;
}
El puntero solo contiene la dirección inicial y el tipo es lo que nos permitirá saber cuántos bytes hay que utilizar y cómo interpretarlos. A continuación, se muestra una representación de la memoria en diferentes situaciones:
- El estado inicial de las variables.
- El resultado de asignar la dirección de
a
al punteropa
. - Cambiar el contenido del puntero 'pa', asignándole un nuevo valor.
Hay que tener presente que el contenido de un puntero a entero es un entero y, por lo tanto, se puede asignar a una variable de tipo entero y se le puede aplicar cualquier operación entera, como por ejemplo, sumarle otro entero o escribirlo por pantalla. Lo mismo ocurre con cualquier otro tipo, o sea, el contenido de un puntero a real es un real y por lo tanto lo podemos tratar como tal.