El utilitario size permite listar los tamaños de las secciones de un archivo binario ejecutable pasado como parámetro, junto con su tamaño total. De esta forma es posible comprender cuánta memoria será utilizada al ejecutar el binario y a su vez examinar cómo está compuesto un ejecutable: cuánta memoria se dedica a datos inicializados; cuánta se utiliza para instrucciones; y cuánta se utiliza para variables o espacio para datos no inicializados.



En ocasiones resulta necesario comprender cómo utiliza la memoria RAM un ejecutable. Cuánta memoria se dedica a instrucciones, cuánta se "pierde" en datos y variables, etc. Por ejemplo cuando se está optimizando un programa o porción de código, al menos en cuanto a uso de memoria respecta.

Veamos un ejemplo analizando cómo se inicializan las variables en el lenguaje C según el scope (ámbito) donde se declaren.

El siguiente programa escrito en C (test.c) abre el archivo "/etc/apt/sources.list" en modo lectura y vuelca su contenido por salida estándar (pantalla) recuperando de a 100 bytes por vez:

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

#define F "/etc/apt/sources.list"
#define BUFFER_SIZE 100

int main()
{

    FILE *f;
    char buffer[BUFFER_SIZE];

    f = fopen(F,"r");

    while ( fgets(buffer,BUFFER_SIZE,f) != NULL ) {
        printf("%s",buffer);
    }

    return 0;
}

Veamos qué resulta al compilar el programa con gcc (GNU C Compiler):

emi@hal9000:/tmp$ gcc -Wall -o test test.c 

Se crea el binario ejecutable test:

emi@hal9000:/tmp$ ls -la test*
-rwxr-xr-x 1 emi emi 16712 Jan  3 11:12 test
-rw-r--r-- 1 emi emi   344 Jan  3 11:12 test.c

Como se trata de un sistema GNU/Linux de 64 bits, el formato del ejecutable es "ELF 63-bit":

emi@hal9000:/tmp$ file test
test: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e6db579250084fcd059c6d0cb8a45b014b83eadd, not stripped

Para conocer el tamaño resultante de cada sección del programa en el espacio de memoria virtual, es necesario contar con el utilitario size. Este es parte del paquete binutils:

emi@hal9000:/tmp$ size test
   text	   data	    bss	    dec	    hex	filename
   1682	    600	      8	   2290	    8f2	test

Por defecto muestra un formato de salida similar a la versión de size de Berkeley. Este formato muestra una única línea por cada binario pasado como parámetro totalizando la memoria utilizada por cada sección. Sin embargo, mediante la opción --format es posible hacer que se comporte como la versión de size de System V:

emi@hal9000:/tmp$ size --format=SysV test
test  :
section              size    addr
.interp                28     680
.note.ABI-tag          32     708
.note.gnu.build-id     36     740
.gnu.hash              36     776
.dynsym               216     816
.dynstr               144    1032
.gnu.version           18    1176
.gnu.version_r         32    1200
.rela.dyn             192    1232
.rela.plt              72    1424
.init                  23    4096
.plt                   64    4128
.plt.got                8    4192
.text                 417    4208
.fini                   9    4628
.rodata                31    8192
.eh_frame_hdr          60    8224
.eh_frame             264    8288
.init_array             8   15848
.fini_array             8   15856
.dynamic              480   15864
.got                   40   16344
.got.plt               48   16384
.data                  16   16432
.bss                    8   16448
.comment               28       0
Total                2318


Este formato detalla todas las secciones encontradas en el programa.

Volviendo al código del programa, dentro de la función "main" se declara un arreglo de 100 bytes que funciona como buffer de disco a memoria:

int main()
{

    FILE *f;
    char buffer[BUFFER_SIZE];

Tal vez el lector despistado o programador novicio pudiera esperar que la sección .bss (espacio de memoria para variables o datos no inicializados) ocupe al menos 100 bytes. No obstante, apenas ocupa 8 bytes:

emi@hal9000:/tmp$ size --format=SysV test | grep .bss
.bss                    8   16448

Esto se debe a que, en el lenguaje C, las variables locales de una función se almacenan en la pila (stack) utilizando un espacio que se conoce como "stack frame", y se inicializan durante la llamada a la función dependiendo de la convención de llamado (caller rules vs. calee rules). Y, tal como se ha visto, el buffer está declarado dentro de la función "main".

Veamos qué pasa ahora si se declara como variable global del programa:

emi@hal9000:/tmp$ cat test.c 
#include <stdio.h>
#include <stdlib.h>

#define F "/etc/apt/sources.list"
#define BUFFER_SIZE 100

char buffer[BUFFER_SIZE];

int main()
{

    FILE *f;

    f = fopen(F,"r");

    while ( fgets(buffer,BUFFER_SIZE,f) != NULL ) {
        printf("%s",buffer);
    }

    return 0;
}

Se observa que la declaración del buffer ha sido movida fuera de la función "main".

emi@hal9000:/tmp$ gcc -Wall -o test test.c 

Al compilar nuevamente, es posible comprobar que la sección .bss ahora ha "engordado" debido a que debe alojar la variable global buffer:

emi@hal9000:/tmp$ size test
   text	   data	    bss	    dec	    hex	filename
   1698	    600	    136	   2434	    982	test

Lo mismo ocurre si se declara a la variable dentro de la función, pero como variable estática:

emi@hal9000:/tmp$ cat test.c 
#include <stdio.h>
#include <stdlib.h>

#define F "/etc/apt/sources.list"
#define BUFFER_SIZE 1000

int main()
{

    FILE *f;
    static char buffer[BUFFER_SIZE];

    f = fopen(F,"r");

    while ( fgets(buffer,BUFFER_SIZE,f) != NULL ) {
        printf("%s",buffer);
    }

    return 0;
}

Se agrega el modificador "static" delante de la variable declarada dentro de la función "main". A su vez el tamaño del arreglo se cambia por 1000:

emi@hal9000:/tmp$ gcc -Wall -o test test.c

Al compilar nuevamente, la sección .bss ocupa al menos 1000 bytes:

emi@hal9000:/tmp$ size test
   text	   data	    bss	    dec	    hex	filename
   1698	    600	   1032	   3330	    d02	test

Tal como se comprueba a través de estos experimentos, no sólo puede size ser útil para optimizar o depurar programas, sino que además puede resultar una buena herramienta para el aprendizaje y comprensión de tecnologías o lenguajes de programación.

Referencias


Tal vez pueda interesarte


Compartí este artículo