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.
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.