Memoria dinámica en C#

Objetivos

  • Objetivo 1…

  • Objetivo 2…

1. Introduccion#

En los programas tipicos, la cantidad de memoria que usan es desconocida. Por ejemplo, cuando alguien emplea un editor de texto nunca sabe cuantas paginas va a escribir. Como el numero de paginas suele estar relacionada con elementos de memoria como matrices presuponer un numero maximo de paginas suele ser ineficiente, pues ello implicaria dos cosas:

  1. Que se tenga gran cantidad de memoria ociosa si el usuario del editor no escribe en todas las paginas reservadas por la aplicacion.

  2. Que el usuario se quede corto de paginas si escribe mucho, pues al tener el editor de texto el numero de paginas predefinido, es posible que el espacio en memoria no sea suficiente para lo que el usuario escribe.

Para dar administrar la memoria de manera mas eficiente, C permite introduce el concepto de reserva dinamica de memoria que no es otra cosa que el proceso de gestion de memoria en tiempo de ejecución segun las necesidades demandadas por la aplicacion. Gracias a esto, es posible que no sea necesario que el editor de texto conozca con antelacion el numero de paginas que debe escribir el usuario, pues gracias a la gestion dinamica de memoria, el editor puede ir creando nuevas paginas a medida que el usuario va demandandolo. En esta seccion se verá las funciones que son empleadas en C para hacer esto posible.

2. Mapa de memoria#

Cuando un programa es cargado en memoria, este se organiza en diferentes regiones memoria conocidas como segmentos que se usan para diferentes propositos.

../_images/CH_02-S05-fig1_3.png

Fig. 65 Mapa de memoria (Memory Layout)#

  • Text Segment: Tambien conocido como code segment. Es el lugar en el que se encuentra el código ejecutable (o binario) asociado al programa.

  • Data Segment: Segmento donde se encuentran las variables y constantes asociadas al programa. Este se divide en:

    • Initialized data segment: Aqui se almacenan las variables globales y estaticas que son inicializadas antes de la ejecución del programa (inicialización explicita).

    • Uninitialized data segment (BSS - Block Started by Symbol): Contiene tolas las variables globales y estadicas que no se inicializaron explicitamente.

  • Heap: Segmento utilizado para la asignación de memoria durante la ejecución del programa. La asignación y liberación de memoria se hace mediante funciones como malloc, calloc, realloc y free. Es importante aclarar que esta es una región de caracter dinamico, pues su tamaño cambia a lo largo de la ejecución del programa.

  • Stack: Región empleada para almacenar las variables locales. Esta región tambien es dinamica ya que su tamaño cambia de manera automatica conforme a medida que se usan funciones.

Existen algunas represetaciones en la cuales los segmentos de Text Segment y Data Segment se combinan como un segmento conocido como Program Code tal y como se muestra a continuación:

../_images/CH_02-S05-fig2.png

Fig. 66 Mapa de memoria (Memory Layout)#

Ejemplos#

Ejemplo 1#

Simule el siguiente código y analice en como sería el mapa de memoria asociado al programa.

int x=5;
char msg[] = "Hello";

int main(int argc, const char* argv[]) {
  int v;
  float pi = 3.14159;
  printf("%d\n",x);
  printf("%f\n",pi);
  return 0;
}

La simulación se puede realizar a continuación:

El resultado de la simulación se muestra a continuación:

../_images/CH_02-S05-fig3.png

Fig. 67 Salida del programa.#

Como se puede ver de la figura anterior, se resaltan los espacios de memoria y las variables que se encuentran en estos:

Espacio de memoria

Variables

Text

Instrucciones

Data

x, msg

Stack

argc, argv, v, pi

Heap

En la siguiente figura se muestra mas claramente como se distribuyen las variables anteriores en el mapa de memoria:

../_images/CH_02-S05-fig4.png

Fig. 68 Asignacion de las variables en el espacio de memoria.#

En la figura anterior, se puede ver que no hay variables en el heap pues no se hicieron asignaciones dinamicas de memoria el cual sera el proximo tema a tratar.

Ejemplo 2#

Dado el siguiente código (tomado del documento Memory & C (link)) como seria el mapa de memoria asociado.

#include <stdio.h>
#include <stdlib.h>

int z = -1;

int main(int argc, char* argv) {
    int x = 3 * sizeof(int);
    char* s_static = "61C";
    char s_stack[4];
    s_stack[0] = '6';
    s_stack[1] = '1';
    s_stack[2] = 'B';
    s_stack[3] = '\0';
    int *heap_arr = (int *)malloc(x);
    heap_arr[0] = 1;
    heap_arr[1] = 2;
    heap_arr[2] = 4;
}

El programa se puede simular a continuación:

La siguiente figura muestra el resultado de la simulación:

../_images/CH_02-S05-fig5.png

Fig. 69 Asignacion de las variables en el espacio de memoria.#

Espacio de memoria

Variables

Text

Instrucciones

Data

z

Stack

argc, argv, x, s static, s_stack[], heap_arr

Heap

heap arr[0], heap arr[3], heap arr[2]

3. Asignando memoria#

Para tratar esta parte abordemos un problema tipico para ver las diferentes formas de solucion desde el punto de vista del manejo de memoria. Supongase que se desean ingresar un conjunto de datos asociados con la temperatura a lo largo del dia, el ingreso de estos datos se hace de manera manual leyendo el numero de registros manuales existentes en una planilla para luego ingresarlos al sistema. Hacer un programa que facilite esta tarea.

Existen diferentes maneras de asignar memoria.

Forma 1#

Declarar un arreglo estatico de tamaño fijo asumiento que el numero de registros a ingresar nunca va a ser mayor que el tamaño fijo previamente definido. Esta forma de declaración es estatica por que la reserva de memoria (arreglo) se hace en tiempo de compilación. A continuación se muestra el código:

#include <stdio.h>

#define NUM_MAX_REG 100

int main() {
  float datos[NUM_MAX_REG]; // Se supone que no se van a ingresar mas de 100 registros
  float reg;
  int numReg;
  printf("Ingrese la cantidad de registros a leer: ");
  scanf("&d",numReg);
  if (numReg > NUM_MAX_REG) {
    printf("ERROR: se pierden %d datos\n", numReg - NUM_MAX_REG);
    numReg = NUM_MAX_REG;
  } 
  for(int i = 0; i < numReg; i++) {
    printf("Dato[%d]: ", i);
    scanf("%f", &reg);    
    *(datos + i) = reg;
  } 
  return 0;
}

Como se podra notar, la forma anterior es ineficiente por razones similares a las expuestas en la introducción.

Forma 2#

Declarar un arreglo dinamico (automatico) de un tamaño especificado en tiempo de ejecución. El siguiente codigo ilustra esta idea:

#include <stdio.h>

int main() {
  float reg;
  int numReg;
  printf("Ingrese la cantidad de registros a leer: ");
  scanf("&d",numReg);
  float datos[numReg];
  for(int i = 0; i < numReg; i++) {
    printf("Dato[%d]: ", i);
    scanf("%f", &reg);    
    *(datos + i) = reg;
  } 
  return 0;
}

El problema de la forma anterior es que el compilador es previo a C99, no permite declarar un arreglo definiendo su tamaño a partir de una variable (siendo numReg) para el caso.

Forma 3#

Declarar un arreglo dinamico de un tamaño especificado en tiempo de ejecución empleando las funciones propias de la libreria estandar para tal caso (malloc y calloc). En lo que respecta al caso, esta es la mejor forma. La descripción y uso de estas funciones sera tratada en breve. Por ahora veamos el codigo asociado.

#include <stdio.h>
#include <stdlib.h>

int main() {
  float *datos;
  float reg;
  int numReg;
  printf("Ingrese la cantidad de registros a leer: ");
  scanf("&d",numReg);
  datos = (float *)malloc(numReg*sizeof(float));
  if (pF != NULL) {
    for(int i = 0; i < numReg; i++) {
      printf("Dato[%d]: ", i);
      scanf("%f", &reg);    
      *(datos + i) = reg;
  } 
  free(datos);
  return 0;
}

Important

Para hacer uso de las funciónes de manejo dinamico de memoria es necesario incluir el archivo cabecera stdlib.h en el código

Ejemplos#

En los siguientes ejemplos se muestra la diferencia entre declarar una variable en el stack y declararla en el heap usando reserva dinamica de memoria.

  1. Creando una variable en el stack

    #include <stdio.h>
    
    int main() {
      int a;
      int *p = &a;
      *p = 5;
      printf("*p = %d\n", *p);
      return 0;
    }
    

    El código anterior se puede simular a continuación:

    La siguiente figura muestre el efecto del codigo anterior:

    ../_images/CH_02-S05-fig6.png

    Fig. 70 Accediendo desde un apuntador a una variable definida en el stack.#

    La siguiente figura muestre el efecto del codigo anterior:

    mem_heap Figura 4. Asignacion dinamica de una variable usando malloc

  2. Creando una variable en el heap

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      int *p;
      p = (int *)malloc(sizeof(int));
      if(p == 0) {
        printf("ERROR: Out of memory\n");
        return 1;
      }
      *p = 5;
      printf("*p = %d\n", *p);
      free(p);
      return 0;
    }
    

    A continuación, se puede simular el código anteriormente mostrado:

    La siguiente figura muestre el efecto del codigo anterior:

    ../_images/CH_02-S05-fig7.png

    Fig. 71 Asignacion dinamica de una variable en el heap usando malloc#

  3. Analizar el siguiente ejemplo:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
      printf("location of code : %p\n", main);
      printf("sizeof(int) = %d\n",sizeof(int));
      int *p = malloc(8);
      int *p2 = malloc(4);
      printf("location of heap : %p\n", p);
      printf("location of p2 in heap : %p\n", p2);
      *p = 3;
      *(p + 1) = 2;
      {
        int x = 3;
        printf("location of stack: %p\n", &x);
      }
      int y = -2;    
      free(p2);
      return 0;
    }
    

    A continuación se muestra la simulación del código:

4. Manejo dinamico de memoria mediante C#

4.1. Operador sizeof#

Este es un operador (no una funcion) que devielve la cantidad en bytes ocupada por una variable o algun tipo de dato. La sintaxis basica de este operador se muestra a continuacion:

size_t sizeof ( type-name )  

El size_t es un tipo de dato unsigned int retornado por el operador sizeof()

Ejemplo

En el siguiente codigo ejemplo se muestran algunos ejemplos del uso de este operador:

#include <stdio.h>

int main() {
  short *p;
  long long a;
  printf("sizeof(short*) = %d\n",sizeof(short*));  // Pasando un tipo de dato
  printf("sizeof(p1) = %d\n",sizeof(p)); // Pasando una variable
  printf("sizeof(short) = %d\n",sizeof(short)); // Pasando un tipo de dato
  printf("sizeof(long long) = %d\n",sizeof(long long)); // Pasando un tipo de dato
  printf("sizeof(long long) = %d\n",sizeof(a)); // Pasando una variable
  printf("sizeof(double*) = %d\n",sizeof(double*)); // Pasando un tipo de dato
  printf("sizeof(char*) = %d\n",sizeof(char*)); // Pasando un tipo de dato
  printf("sizeof(char) = %d\n",sizeof(char)); // Pasando un tipo de dato
  return 0;
}

El código anterior puede ser simulado a continuación:

A continuacion se muestra la salida en el simulador para diferentes tipos de datos:

../_images/CH_02-S05-fig8.png

Fig. 72 Tamaño de diferentes tipos de datos.#

4.2. Manejo dinamico de memoria#

El manejo de la memoria en el heap consiste basicamente en reservar y liberar memoria en este segmento. Para reservar memoria se emplea la función malloc (o similares), mientras que para liberar (una porción de memoria previamente asignada) se emplea la función free. A diferencia de java, en C el proceso de gestion de memoria es manual por lo que es fundamental, despues de reservar memoria usando malloc, llamar a la función free si no se necesita usar mas la porción previamente reservada. Olvidar esto ultimo genera fugar de memoria (memory leaks) haciendo que se consuman recursos de memoria innecesariamente y degradando el funcionamiento de la aplicación.

A continuación vamos a ver con mas detalle las diferentes funciones para la gestión de memoria en el heap.

4.2.1. Asignación de memoria: Memory Allocation o malloc#

La función malloc permite reservar un bloque de memoria (sin inicializar) en el heap. El prototipo de esta función se muestra a continuación:

malloc

Sintaxis:

void * malloc (size_t size)

Donde:

  • size: Numero de bytes que seran reservados

La funcion retorna la direccion en el heap a partir de la cual se reservó el tamaño de memoria solicitado o retorna NULL si no es posible reservar la cantidad de memoria. Cabe recordar que void* indica que la dirección retornada es genérica, es decir, en esa dirección se puede almacenar cualquier tipo de variable.

Usando malloc tambien es posible declarar arrays en memoria dinamica pasando el tamaño del array en bytes. Asi por ejemplo, para declara un array de N elementos, el valor pasado como parametro a la funcion malloc sera N*sizeof(dataTipe).

4.2.2. Liberación de memoria: free#

La función free libera un bloque de memoria previamente reservado en el heap. El prototipo de esta función se muestra a continuación:

free

Sintaxis:

void free(void* pointer);

Donde:

  • pointer: apuntador con la dirección del bloque de memoria (previamente reservado con malloc o con calloc) a liberar.

Ejemplos

Para comprender el uso del par de funciones anteriormente mencioadas se muestran algunos códigos a continuación:

  1. Hacer un programa que reserve y libere una variable tipo int en el heap

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      int *p, *q;
      p = (int *)malloc(sizeof(int));
      q = p;
      *p = 10;
      printf("%d\n",*q);
      *q = 20;
      printf("%d\n",*q);
      free(p);  // Tambien para el caso puede ser q
      return 0;
    }
    

    La simulación del programa anterior se muestra a continuación:

    La salida del codigo anterior se muestra a continuacion:

    ../_images/CH_02-S05-fig9.png

    Fig. 73 Funcionamiento código ejemplo 1#

  2. Hacer un programa que reserve y libere dos variables tipo int en el heap

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      int *p, *q;
      p = (int *) malloc(sizeof(int));
      q = (int *) malloc(sizeof(int));
      *p = 10;
      *q = 20;
      printf("*p = %d; *q = %d\n", *p, *q);
      *p = *q;
      printf("*p = %d; *q = %d\n", *p, *q);
      free(p);  
      free(q);  
      return 0;
    }
    

    La simulación se muestra a continuación:

    La salida de ejemplo se puede ver en la siguiente figura:

    ../_images/CH_02-S05-fig10.png

    Fig. 74 Funcionamiento código ejemplo 2#

  3. Crear dinamicamente un array de 10 elementos y llenarlo de ceros.

    #include <stdio.h>
    #include <stdlib.h>
    
    #define TAM 10 
    
    int main() {
      int *p, *q;
      p = (int *) malloc(TAM*sizeof(int));
      for (int i = 0; i < TAM; i++) {
        *(p + i) = 0; // p[i] = 0
      }
      free(p);    
      return 0;
    }
    

    La simulación del código se muestra a continuación:

    La salida del codigo anterior se muestra a continuacion:

    ../_images/CH_02-S05-fig11.png

    Fig. 75 Salida del código del ejemplo 3#

Tip

Asignar NULL a un apuntador no es obligatorio, pero es buena practica. Esto para evitar algun error si el apuntador es erroneamente utilizado despues de que la memoria ha sido liberada.

  1. Crear dinamicamente un array de 10 elementos y llenarlo de ceros, pero esta vez haga uso de la buena practica de programacion para entenderla.

    #include <stdio.h>
    #include <stdlib.h>
    
    #define TAM 10 
    
    int main() {
      int *p, *q;
      p = malloc(TAM*sizeof(int));
      for (int i = 0; i < TAM; i++) {
        *(p + i) = 0; // p[i] = 0
      }
      free(p); 
      p = NULL;
      return 0;
    }
    

    La simulación del código se muestra a continuación:

    La salida del codigo anterior se muestra a continuacion cuando se hace la reserva dinamica de memoria:

    ../_images/CH_02-S05-fig12.png

    Fig. 76 Salida codigo 4 tras la reserva con malloc.#

    El efecto despues de liberar memoria y poner el apuntador a NULL:

    ../_images/CH_02-S05-fig13.png

    Fig. 77 Salida codigo tras aplicar del free y poner el apuntador en valor NULL.#

  2. Definir e inicializar a cero un dos array de 4 elementos. Uno de estos arrays estará en el stack y el otro estara en el heap

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
    
      // Reserva en el stack
      int A_stack[4];
      printf("sizeof(int) = %d\n",sizeof(int));
     
     
      for(int i = 0; i < 4; i++) {
        A_stack[i] = 0;
      }
     
      // Reserva en el heap
      int *A_heap = malloc(16);  // 4*4 = 4*sizeof(int) = 16   
     
     
      for(int i = 0; i < 4; i++) {
        // A[i] = 0;
        // *A_heap = 0;
        // A_heap++;
        *(A_heap + i) = 0; //A_heap[i] = 0;
      }
      free(A_heap);
      return 0;
    }
    

    La simulación del código se puede realizar a continuación:

  3. Haciendo uso de malloc hacer una copia de una cadena de caracteres definida en el stack en otra definida en el heap. Despues de usar la cadena que se definió en el heap usar free para liberarla.

    #include <stdio.h>
    #include <string.h>
    
    #define UDEA "UdeA"
    
    int main() {  
      char *udea_1 = UDEA;
      char udea_2[] = UDEA;
      printf("%s\n", udea_1);
      printf("%s\n", udea_2);
      char *udea_3;
      int tam = strlen(udea_1);
      printf("%d\n", tam);
      udea_3 = (char *)malloc((tam+1)*sizeof(char));
      strcpy(udea_3, udea_1);
      printf("%s\n", udea_3);
      free(udea_3);
      udea_3 = NULL;
      return 0;
    }
    

    El código puede ser simulado a continuación:

4.2.3. Asignación de memoria con inicializacion: calloc#

La función calloc permite reservar e inicializat a 0 un bloque de memoria en el heap. El prototipo de esta función se muestra a continuación:

calloc

Sintaxis:

void * calloc (size_t num, size_t size)

Donde:

  • num: numero de elementos consecutivos a reservar.

  • size: Tamaño en bytes de cada elemento.

El espacio total reservado en la funcion es de numElements*size. Normalmente, la funcion retorna un apuntador que contiene la direccion inicial del bloque reservado en el heap. En caso de que no haya suficiente memoria disponible la funcion retornara NULL.

Ejemplos

A continuación, se muestran algunos ejemplos haciendo de uso de la función calloc

  1. Declare una en el heap un dato double inicializado a 0.

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      double *p;
      p = (double *)calloc(1, sizeof(double));
      if(p == 0) {
        printf("ERROR: Out of memory\n");
        return -1;
      }
      printf("*p = %.1lf\n", *p);
      free(p);
      p = NULL;
      return 0;
    }
    

    La simulación del programa anterior se muestra a continuación:

    La salida del codigo anterior se muestra a continuacion:

    ../_images/CH_02-S05-fig14.png

    Fig. 78 Salida codigo tras la reserva con calloc.#

  2. Crear dinamicamente un array de 10 elementos enteros y llenarlo de ceros por medio de la funcion calloc.

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      int *p;
      p = (int *)calloc(10, sizeof(int));
      if(p == 0) {
        printf("ERROR: Out of memory\n");
        return -1;
      }
      free(p);
      p = NULL;
      return 0;
    }
    

    La simulación del programa anterior se muestra a continuación:

4.2.4. Reasignación de memoria: Memory Re-allocation o realloc#

La función realloc permite redimensionar un bloque de memoria previamente reservada con malloc. El prototipo de esta función se muestra a continuación.

realloc

Sintaxis:

void* realloc(void* ptr, size_t new_size)

Donde:

  • ptr: Puntero a memoria.

  • new_size: Nuevo tamaño requerido.

Si el tamaño es reducido, hay datos que se pueden perder. Si el tamaño es incrementado y la funcion es incapaz de extender la localizacion existente, esta asignara un nuevo espacio de memoria y copiara los datos a traves de esa retornando un puntero a la memoria nuevamente asignada.

Ejemplos

A continuación, se muestran algunos ejemplos haciendo de uso de la función realloc

  1. El siguiente ejemplo muestra un caso de uso de la función realloc.

    #include <stdio.h>
    #include <stdlib.h>
    
    #define TAM1 10 
    #define TAM2 5
    #define TAM3 15
    
    int main() {
      int *p;
      p = (int *)malloc(TAM1*sizeof(int));
      printf("Ubicacion del apuntador tras el malloc: %p\n",p);
      for (int i = 0; i < TAM1; i++) {
        *(p + i) = i + 1; // p[i] = 0
      }
      p = realloc(p,TAM2*sizeof(int));
      printf("Ubicacion del apuntador tras el primer realloc: %p\n",p);
      p = realloc(p,TAM3*sizeof(int)); 
      printf("Ubicacion del apuntador tras el segundo realloc: %p\n",p);
      free(p);   
      p = NULL;
      return 0;
    }
    

    La simulación del programa anterior se muestra a continuación:

    La salida del codigo anterior se muestra a continuacion:

    ../_images/CH_02-S05-fig15.png

    Fig. 79 Efecto de usar realloc.#

  2. Analice y simule el siguiente código.

    #include <stdio.h>
    #include <stdlib.h>
    
    #define BUFF_SIZE 3
    
    void printArray(int *A, int tam); 
    
    int main() {
      int array[] = {1,2,4,6};
      int alloc = BUFF_SIZE;
      int *buf = (int *)calloc(alloc,sizeof(int));
      if(buf == 0) {
        printf("ERROR: Out of memory\n");
        return -1;
      }
      int size_array = sizeof(array)/sizeof(array[0]);
      printf("Inicio: buf =");
      printArray(buf, alloc);
      printf("\n");
      while(size_array > alloc) {
        alloc *= 2;
        buf = realloc(buf, alloc*sizeof(int));
      }
      for (int i = 0; i < alloc;i++) {
        if(i < size_array) {
          *(buf + i) = *(array + i); // buf[i] = array[i]
        }
        else {
          *(buf + i) = 0;
        }
      }
      printf("Fin: buf =");
      printArray(buf, alloc);
      printf("\n");
      free(buf);
      buf = NULL;
      return 0;
    }
    
    void printArray(int *A, int tam) {
      printf("[ ");
      for(int i = 0; i < tam; i++) {
        printf("%d ", *(A + i));
      }
      printf("]");
    }
    

    A continuación, se puede simular el código anterior:

5. Miselanea de ejemplos#

  1. Usando malloc crear la matriz irregular (jagged arrays) mostrada en la siguiente figura:

    ../_images/CH_02-S05-fig16.png

    Fig. 80 Matriz irregular.#

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      int F = 3;
      int C[] = {4,2,3};
      int val = 0;
    
      // Reserva de espacio para la matriz irregular en el heap
      int** jagged_array = (int **)malloc(F*sizeof(int *));
      for(int i = 0; i < F; i++) {
        // jagged_array[i] = (int *)malloc(C[i]*sizeof(int));
        *(jagged_array + i) = (int *)malloc(C[i]*sizeof(int));
      }
    
      // Llenado de la matriz irregular
      for(int i = 0; i < F; i++) {
        for(int j = 0; j < C[i]; j++) {
          // jagged_array[i][j] = ++val;
          *(*(jagged_array + i) + j) = ++val;
          // printf("%d ", jagged_array[i][j]);
          printf("%d ",*(*(jagged_array + i) + j));
        }
        printf("\n");
      }
    
      // Liberacion del espacio en el heap asociado a la matriz irregular
      for(int i = 0; i < F; i++) {
        free(*(jagged_array + i));
        *(jagged_array + i) = NULL;
      }  
      free(jagged_array);
      jagged_array = NULL;
    
      return 0;
    }
    

    La simulación del código anterior se puede realizar a continuación:

  2. Implementar una lista enlazada sencilla.

    El código solución se muestra a continuación (Para simularlo usa el siguiente link)

    #include <assert.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    struct node {
      int data;
      struct node * next;
    };
    
    void print_list(struct node *head);
    struct node * insert_at_end(struct node *head, int data);
    int delete_at_front(struct node **phead); 
    void delete_at_begin(struct node **phead);
    void addOne(int *pn);
    
    
    int main() {
      int n = 100;
      addOne(&n);
      printf("n = %d\n", n);
      struct node * head = NULL;
      head = insert_at_end(head, 10);
      print_list(head);
      head = insert_at_end(head, 20);
      print_list(head);
      head = insert_at_end(head, 30);
      print_list(head);
      delete_at_begin(&head);
      print_list(head);
      delete_at_begin(&head);
      print_list(head);
      delete_at_begin(&head);
      print_list(head);
      return 0;
    }
    
    int delete_at_front(struct node **phead) {
      struct node * first = *phead;
      assert(first != NULL);
      *phead = first->next;
      int data = first->data;
      free(first);
      return data;
    }
    
    struct node * insert_at_end(struct node *head, int data) {
      // create a new node.
      struct node * new_node = malloc(sizeof(struct node));
      assert(new_node != NULL);
      new_node->data = data;
      new_node->next = NULL;
    
      // list is empty.
      if (head == NULL) {
        head = new_node;
        return head;    
      }
    
      // list has some elements already.
      struct node *current = head;
      while (current->next != NULL) {
        current = current->next;
      }
    
      current->next = new_node;
      return head;
    }
    
    void print_list(struct node *head) {
      struct node * current = head;
      if (current == NULL) {
        printf("Empty list.\n");
        return;
      } else {
        while (current) {
          printf("|%d|%p| -> ", current->data, current->next);
          current = current->next;
        } 
        printf("\n");
      } 
    }
    
    void delete_at_begin(struct node **phead) {
      struct node *first = *phead;
      *phead = (*phead)->next;
      free(first);
    }
    
    void addOne(int *pn) {
      *pn = *pn + 1;
    }
    

6. Enlaces#

  • https://bytesoftheday.wordpress.com/2014/07/04/q14/

  • https://www.cs.princeton.edu/courses/archive/spring20/cos217/

  • https://embeddedwala.com/Blogs/embedded-c/memory-layout-of-c-program

  • https://www.cs.mtsu.edu/~cs2170/C++labs/lab18/OSmemlayout.pdf

  • https://d1b10bmlvqabco.cloudfront.net/attach/j6fe5friemd22w/hzd1madqsie3ts/j7kw6i4tmqf8/61C_Note_1_Memory.pdf

  • https://ocw.mit.edu/courses/6-s096-introduction-to-c-and-c-january-iap-2013/bba9056d5290198d563edc47dfcff0e9_MIT6_S096_IAP13_lec3.pdf

  • https://cs61c.org/su24/

  • http://wla.berkeley.edu/~cs61c/fa17/

  • https://www.cs.princeton.edu/courses/archive/fall07/cos217/index.html

  • https://web2.qatar.cmu.edu/~mhhammou/15122-s23/lectures/21-cmem/writeup/pdf/main.pdf

  • https://cs.gmu.edu/~zduric/cs262/Slides/teoX.pdf

  • https://d1b10bmlvqabco.cloudfront.net/attach/j6fe5friemd22w/hzd1madqsie3ts/j7kw6i4tmqf8/61C_Note_1_Memory.pdf

  • https://www.cs.princeton.edu/courses/archive/fall07/cos217/

  • https://www.cs.mtsu.edu/~cs2170/C++labs/lab18/OSmemlayout.pdf

  • https://web2.qatar.cmu.edu/~mhhammou/15122-s23/lectures/21-cmem/writeup/pdf/main.pdf

  • https://www.cs.princeton.edu/courses/archive/spr24/cos126/schedule/

  • https://github.com/vishwa27yvs/Intro-to-Computer-Science-COS-126

  • https://www.berthon.eu/wiki/foss:wikishelf:linux:memory

  • http://resources.infosecinstitute.com/system-address-map-initialization-in-x86x64-architecture-part-1-pci-based-systems/#gref

  • https://fypandroid.wordpress.com/2011/01/17/anatomy-of-a-program-in-memory/

  • https://www.securitysift.com/windows-exploit-development-part-1-basics/

  • https://www.ibm.com/developerworks/library/j-nativememory-linux/

  • https://gabrieletolomei.wordpress.com/miscellanea/operating-systems/in-memory-layout/

  • http://www.cs.utexas.edu/users/fussell/cs310h/lectures/Lecture_17-310h.pdf

  • https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-087-practical-programming-in-c-january-iap-2010/lecture-notes/

  • https://stackoverflow.com/questions/2128728/allocate-matrix-in-c

  • https://www.geeksforgeeks.org/dynamically-allocate-2d-array-c/

  • https://www.programiz.com/c-programming/c-dynamic-memory-allocation

  • https://www.cs.swarthmore.edu/~newhall/unixhelp/C_arrays.html