¿Variables locales o globales? Analizando su impacto

Este artículo es el número 1 de 2 de la serie Programación Avanzada en C

Explicamos el impacto de las variables locales y globales en nuestros programas en C para Amstrad CPC. Vemos cómo el compilador de C incluído en CPCtelera convierte nuestro código a ensamblador. Utilizamos un sencillo ejemplo que dibuja una matriz en pantalla usando printf. Primero vemos las diferencias de espacio en memoria. Después analizamos a qué se deben. Por último, observamos cómo estas diferencias se plasman en el binario que nuestro Amstrad cargará en memoria para ejecutar.

El programa analizado simula un pequeño mapa de lo que podría ser un juego. El mapa se define como una matriz de 10x10 elementos de tipo u8 (8 bits sin signo). La pregunta viene a la hora de crear y almacenar ese mapa. ¿Lo creamos como variable local o global? A continuación mostramos los dos códigos de ejemplo.

Primera versión: map definido como variable local a main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <cpctelera.h>
#include <stdio.h>
 
void main(void) {
   const u8 map[10][10] = {
         { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
      ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   };
   u8 i, j;
 
   for(i=0; i < 10; i++) {
      for (j=0; j < 10; j++)
         printf("%d ", map[i][j]);
      printf("\n\r");
   }
 
   // Loop forever
   while(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <cpctelera.h>
#include <stdio.h>
 
const u8 map[10][10] = {
      { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
   ,  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
};
 
void main(void) {
   u8 i, j;
 
   for(i=0; i < 10; i++) {
      for (j=0; j < 10; j++)
         printf("%d ", map[i][j]);
      printf("\n\r");
   }
 
   // Loop forever
   while(1);
}
10 ' Definir la matriz 
20 DEFINT a-z:DIM map(9,9)
30 ' Rellenar la matriz
40 FOR i=0 TO 9
50  FOR j=0 TO 9
60   map(i,j)=j+1
70  NEXT j
80 NEXT i
90 ' Dibujar la matriz en pantalla
100 MODE 1:PEN 1:PAPER 0:INK 1,24
110 FOR i=0 TO 9
120  FOR j=0 TO 9
130   PRINT map(i,j);
140  NEXT j
150  PRINT
160 NEXT i
170 GOTO 170: ' Loop forever

Para poder responder a la pregunta, compilamos ambos códigos con CPCtelera. El primer resultado importante es la diferencia de tamaños de los binarios resultantes:

  • map local:  3671 bytes
  • map global: 3140 bytes (-531 bytes)

Con sólo cambiar la definición de la variable map de local a global ahorramos la friolera de 531 bytes (cuando la matriz sólo ocupa 100 bytes). ¿A qué se debe esta gran ganancia? Como explicamos en el vídeo, se debe la necesidad de crear y rellenar la matriz en tiempo de ejecución. Cuando la matriz es local a main, es necesario reservar los 100 bytes y rellenarlos cada vez que main sea llamado (en tiempo de ejecución). Para rellenar esos100 bytes es necesario hacerlo con código, copiando esos valores al espacio recién reservado. Este código que contiene y copia los propios valores de la matriz ocupa el espacio de diferencia de las dos versiones. Además, no sólo ocupamos más espacio por el código que copia, también necesitaremos tiempo en ejecución, ya que todo ese código gastará ciclos en rellenar la matriz recién creada.

Bien es verdad que el compilador SDCC podría hacerlo mejor. Podría tener una versión de 100 bytes de los valores iniciales de la matriz y hacer una copia usando 3 o 4 instrucciones de ensamblador para copiar esos 100 bytes seguidos al espacio reservado. Esto reduciría la diferencia que vemos de 531 bytes a unos 110-120 bytes. Aún así, seguiríamos teniendo diferencia tanto en espacio como en ejecución. Aunque, en este segundo caso, podría ser asumible si tenemos la necesidad de restaurar los valores de la matriz.

La consecuencia más importante que obtenemos de este experimento es que necesitamos conocer el detalle de lo que sucede cuando programamos. Programar sólo en alto nivel sin prestar atención a lo que sucede por debajo nos impediría ver estas diferencias tan importantes. El experimento lo hemos hecho con una matriz 10x10, pero si necesitáramos una matriz 100x100, la diferencia entre ambas versiones serían 50 KB, que ni siquiera nos cabrían en memoria. En ordenadores modernos estas diferencias pueden ser distintas, pero también existen, y debemos igualmente conocerlas para programar bien.

Al final, lo más importante nunca es la herramienta (el lenguaje de programación, en este caso), sino el conocimiento que tengamos para manejarla.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.