Curso-lenguaje-C/fundamentos-programacion/PEC2/README.md

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(oat));
  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 oat 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;
  oat 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;
  oat b=4.5;
  int *pa=&a;
  oat *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:

  1. El estado inicial de las variables.
  2. El resultado de asignar la dirección de a al puntero pa.
  3. 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.

Volver arriba