En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

sábado, 8 de julio de 2017

El bueno, el feo y el VLA
cómo usar los Variable Length Arrays en C - pt.3

Aquí estamos, y, como prometido, este mes vamos a hablar de un pariente cercano de los VLAs, o sea de la función alloca()... ¿será un bueno, un feo o un malo?
¡hola, soy el spoiler de este post!
Entonces, he añadido código en el programa de test para probar la alloca(). Y, para terminar a lo grande, he añadido código para probar la malloc() del C++, o sea la new (después del problemático test de std::vector del último post era correcto acabar el argumento con algo con mejore prestaciones, para que no se diga que tengo algo en contra del C++...). Así que vamos a utilizar programa C++ del post pasado (ya que era prácticamente idéntico a la versión C): os enseño otra vez el main() y las dos funciones de test añadidas (para reconstruir el programa completo es suficiente consultar los dos post anteriores y hacer un poco cut-and-paste). ¡Vamos con el código!
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <vector>

#define MYSIZE  1000000

// variable dummy con el fin de evitar el vaciado total de las funciones usando G++ -O2
int avoid_optimization;

// prototipos locales
void testVLA(int size);
void testMallocVLA(int size);
void testStackFLA(int dum);
void testHeapFLA(int dum);
void testAllocaVLA(int size);
void testVectorVLA(int size);
void testNewVLA(int size);
void runTest(int iterations, void (*funcptr)(int), int size, const char *name);

// función main()
int main(int argc, char* argv[])
{
    // test argumentos
    if (argc != 2) {
        // error args
        printf("%s: wrong arguments counts\n", argv[0]);
        printf("usage: %s vla iterations [e.g.: %s 10000]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // extrae iteraciones
    int iterations = atoi(argv[1]);

    // ejecuta test
    runTest(iterations, &testVLA, MYSIZE, "testVLA");
    runTest(iterations, &testMallocVLA, MYSIZE, "testMallocVLA");
    runTest(iterations, &testStackFLA, 0, "testStackFLA");
    runTest(iterations, &testHeapFLA, 0, "testHeapFLA");
    runTest(iterations, &testAllocaVLA, MYSIZE, "testAllocaVLA");
    runTest(iterations, &testVectorVLA, MYSIZE, "testVectorVLA");
    runTest(iterations, &testNewVLA, MYSIZE, "testNewVLA");

    // sale
    return EXIT_SUCCESS;
}

// función testAllocaVLA()
void testAllocaVLA(
    int size)       // size para alloca()
{
    int *allocavla = (int*)alloca(size * sizeof(int));

    // loop de test
    for (int i = 0; i < size; i++)
        allocavla[i] = i;

    // instrucción con el fin de evitar el vaciado total de las funciones usando G++ -O2
    avoid_optimization = allocavla[size / 2];
}

// función testNewVLA()
void testNewVLA(
    int size)       // size para new
{
    int *newvla = new int[size];

    // loop de test
    for (int i = 0; i < size; i++)
        newvla[i] = i;

    // instrucción con el fin de evitar el vaciado total de las funciones usando G++ -O2
    avoid_optimization = newvla[size / 2];

    delete[] newvla;
}
Como se puede ver, las dos funciones añadidas están perfectamente alineadas estilísticamente con las demás que ya había propuesto y son, como siempre, hiper-comentadas, así que ni siquiera tengo que detenerme en largas explicaciones. ¿Y los resultados de las pruebas? ¡Vamos a verlos!
aldo@ao-linux-nb:~/blogtest$ g++ vlacpp.cpp -o vlacpp
aldo@ao-linux-nb:~/blogtest$ ./vlacpp 2000
testVLA       -  Tiempo transcurrido: 4.318492 segundos
testMallocVLA -  Tiempo transcurrido: 3.676805 segundos
testStackFLA  -  Tiempo transcurrido: 4.339859 segundos
testHeapFLA   -  Tiempo transcurrido: 4.340040 segundos
testAllocaVLA -  Tiempo transcurrido: 3.678644 segundos
testVectorVLA -  Tiempo transcurrido: 10.934088 segundos
testNewVLA    -  Tiempo transcurrido: 3.679624 segundos
aldo@ao-linux-nb:~/blogtest$ g++ -O2 vlacpp.cpp -o vlacpp
aldo@ao-linux-nb:~/blogtest$ ./vlacpp 2000
testVLA       -  Tiempo transcurrido: 0.746956 segundos
testMallocVLA -  Tiempo transcurrido: 0.697261 segundos
testStackFLA  -  Tiempo transcurrido: 0.696310 segundos
testHeapFLA   -  Tiempo transcurrido: 0.700047 segundos
testAllocaVLA -  Tiempo transcurrido: 0.691677 segundos
testVectorVLA -  Tiempo transcurrido: 1.384563 segundos
testNewVLA    -  Tiempo transcurrido: 0.695037 segundos
Entonces, ¿qué se puede decir? Los resultados de los test de los post anteriores ya los hemos ampliamente comentados, por lo que ahora sólo podemos añadir que: alloca() es muy rápida, ya que es, en práctica, una malloc() en el stack (y utilizándola de manera más apropriada, podría/debería ser la más rápida del grupo). ¿Y la new? Bueno, se comporta (como se esperaba) muy bien, también porque, casi siempre, la new internamente utiliza la malloc().

De acuerdo, la alloca() es rápida, pero lo es (sólo un poco menos) también un VLA, y esto no le ha salvado de ser elegido como el malo de la película. Así que vamos a hacer de nuevo una lista de pros y contras, y a ver cuál es el lado más pesado. Veamos primero los pros:
  1. la alloca() es muy rápida, ya que usa el stack en lugar del heap.
  2. la alloca() es fácil de usar, es una malloc() sin free(). La variable alocada tiene un scope a nivel de función, entonces sigue siendo válida hasta que la función retorna al caller, exactamente como cualquier variable automática local (también un VLA funciona más o menos así, pero su scope es a nivel de bloque, no de función, y esto es, probablemente, un punto a favor de los VLAs).
  3. para la razón explicada en la sección 2, la alloca() no deja residuos de memoria en caso de errores graves en las actividades de una función (con malloc() + free() no es tan fácil lograr esto). Y si estás acostumbrado a utilizar cositas como longjmp() las ventajas en este sentido son grandes.
  4. debido a su aplicación interna (sin entrar en más detalles) la alloca() no provoca la fragmentación de memoria. 
    ¡Uh, qué bien! ¿Y los contras?
    1. la gestión de errores es problemática, porque no hay forma de saber si la alloca() ha alocado bien o ha causado un stack overflow (en este caso provoca efectos similares a los de un error para recursión infinita)... uh, esto es exactamente el mismo problema de los VLAs.
    2. la alloca() no es muy portable, ya que no es una función estándar y su funcionamiento/presencia depende en gran medida del compilador en uso.
    3. la alloca() es error prone (parte 1): hay que utilizarla con cuidado, ya que induce, por lo general, a errores cómo utilizar la variable asignada cuando ya no es válida (pasarla con un return o insertarla en una estructura de datos externa a la función, por ejemplo)... pero nosotros somos buenos programadores y este punto no nos da miedo, ¿no?
    4. la alloca() es error prone (parte  2): hay problemas aún más sutiles que considerar en el uso, por ejemplo puede ser MUY peligroso poner una alloca() dentro de un bucle o de una función recursiva (¡pobre stack!) o en una función inline (que utiliza el stack de manera que choca un poco con la forma de utilizar el stack de la alloca()) ... pero nosotros somos buenos programadores y este punto no nos da miedo, ¿no?
    5. la alloca() es error prone (parte 3): alloca() utiliza el stack, que normalmente está limitado con respecto al heap (especialmente en entornos embedded que son muy frecuentados por los programadores C...). Así agotar el stack y causar un stack overflow es fácil (y difícil de controlar, véase el punto 1)... pero nosotros somos buenos programadores y este punto no nos da miedo, ¿no?
      Vale, ¿las conclusiones? Habría motivos para declarar la alloca() como otro malo (la misma suerte que el VLA), pero teniendo en cuenta las importantes ventajas y, sobre todo, porque hoy estoy de buen humor, le declararemos solo como feo (¿habéis visto el spoiler en la imagen arriba?). De todos modos utilizar la alloca() con mucho cuidado, ¡hombre prevenido vale por dos!

      ¡Hasta el próximo post!

      sábado, 10 de junio de 2017

      El bueno, el feo y el VLA
      cómo usar los Variable Length Arrays en C - pt.2

      Así que, ¿dónde estábamos? Ah sí, en el último post (que acabáis de leer otra vez, ¿verdad?) hemos aprobado (con dudas) los VLAs, que son fáciles de usar, útiles y con un rendimiento excelente, pero entonces... ¿por qué dije que eran aptos para el rol del malo en el legendario "El bueno, el feo y el malo"?
      no confiáis en el VLA, ¡os lo dice el bueno!
      Así de fácil: además de los (considerables) pros también hay algunos (pesados) contras. Antes de seguir siempre hay que recordar que un VLA se asigna dinámicamente en el stack, como una variable automática con scope limitado al bloque de código en la que la alocacion se lleva a cabo: considerando esto los (principales) problemas posibles son:
      1. la gestión de errores es problemática, porque no hay forma de saber si el VLA ha sido alocado bien o ha causado un stack overflow (en este caso provoca efectos similares a los de un error para recursión infinita).
      2. el tamaño del VLA se decide en run-time, por lo que el compilador debe hacer juegos un poco extraños: dependiendo de la aplicación, es posible que una parte (a veces importante) del stack de una función se reserve para un VLA, limitando mucho la memoria local disponible. O sea: el stack overflow està siempre alrededor de la esquina.
      3. la portabilidad del código se desmorona un poco: el código se vuelve muy compiler-dependent y, sobre todo, ya que una buena parte de los programadores C escriben también código para sistemas embedded (donde el stack está, a menudo, limitado) resulta complicado el porting de funciones de aplicaciones normales a aplicaciones embedded. Funciones que, tal vez, dejarían de funcionar por razones misteriosas (bueno, no tan misteriosas).
      4. Por último, pero no menos importante: tal vez por las razones mencionadas anteriormente (u otras más) de C11 hacia adelante los VLAs son opcionales y están sujetas a una variable del compilador __STDC_NO_VLA__: mala señal.
      Entonces, ¿qué? Mejor no usarlos o utilizarlos con las precauciones necesarias, porque las alternativas no faltan. ¡Malo encontrado!

      Y ahora tenemos que buscar a alguien que se adapte a los papeles de bueno y feo. Vale, para el bueno no hay problema, el candidato ideal es la nuestra querida amiga malloc(), que es siempre una garantía y ha salido muy bien de los test. Sobre malloc() es inútil gasta mas palabras, es un punto fijo del C y ya hemos hablado a fondo sobre ella aquí.

      Y el feo? Bueno, para encontrar uno adecuado tendremos, por desgracia, entrar en el lado oscuro de la fuerza, es decir, en el territorio C++...

      (...Abro un paréntesis: nunca hablo de temas que no conozco, porque creo que es estúpido hacerlo. Por ejemplo: no entiendo nada de motos y, se lo aseguro, nadie ha tenido el honor de escucharme charlar sobre el mundial de MotoGP. Sigo una filosofía, que, por desgracia, no es seguida por muchas personas, o sea: "mejor callar que hablar sólo para dar aire a la boca". Precisamente gracias a esta coherencia creo que tengo las cualidades para hablar de C++: yo lo he usado en paralelo a mi amado C para casi treinta años (!), y, modestia aparte, creo que se usarlo muy bien. Así que puede hablar sobre el, para bien o para mal. Cierro el paréntesis...).


      Entonces, he retomado el ejemplo C del post anterior y (haciendo el mínimo sindical de modificaciones) le he transformado en codigo C++, con el fin, por lo tanto, de añadir un nuevo test que usa std::vector (esto es un objeto particularmente querido para los C++ lovers, que lo usan también para condimentar la ensalada). Para evitar de repetir todo el código del ultimo post os paso sólo el main() y la nueva función de test (el resto es, prácticamente, idéntico). ¡Vamos con el código!
      #include <stdio.h>
      #include <time.h>
      #include <stdlib.h>
      #include <vector>
      
      #define MYSIZE  1000000
      
      // variable dummy con el fin de evitar el vaciado total de las funciones usando G++ -O2
      int avoid_optimization;
      
      // prototipos locales
      void testVLA(int size);
      void testMallocVLA(int size);
      void testStackFLA(int dum);
      void testHeapFLA(int dum);
      void testVectorVLA(int size);
      void runTest(int iterations, void (*funcptr)(int), int size, const char *name);
      
      // función main()
      int main(int argc, char* argv[])
      {
          // test argumentos
          if (argc != 2) {
              // error args
              printf("%s: wrong arguments counts\n", argv[0]);
              printf("usage: %s vla iterations [e.g.: %s 10000]\n", argv[0], argv[0]);
              return EXIT_FAILURE;
          }
      
          // extrae iteraciones
          int iterations = atoi(argv[1]);
      
          // ejecuta test
          runTest(iterations, &testVLA, MYSIZE, "testVLA");
          runTest(iterations, &testMallocVLA, MYSIZE, "testMallocVLA");
          runTest(iterations, &testStackFLA, 0, "testStackFLA");
          runTest(iterations, &testHeapFLA, 0, "testHeapFLA");
          runTest(iterations, &testVectorVLA, MYSIZE, "testVectorVLA");
      
          // sale
          return EXIT_SUCCESS;
      }
      
      // función testVectorVLA()
      void testVectorVLA(
          int size)       // size para std::vector
      {
          std::vector<int> vectorvla(size);
      
          // loop de test
          for (int i = 0; i < size; i++)
              vectorvla[i] = i;
      
          // instrucción con el fin de evitar el vaciado total de las funciones usando G++ -O2
          avoid_optimization = vectorvla[size / 2];
      }
      Compilando (con/sin optimizaciones) y ejecutando este código los resultados son los siguientes:
      aldo@ao-linux-nb:~/blogtest$ g++ vlacpp.cpp -o vlacpp
      aldo@ao-linux-nb:~/blogtest$ ./vlacpp 2000
      testVLA       -  Tiempo transcurrido: 4.274441 segundos
      testMallocVLA -  Tiempo transcurrido: 3.641508 segundos
      testStackFLA  -  Tiempo transcurrido: 4.340430 segundos
      testHeapFLA   -  Tiempo transcurrido: 4.312986 segundos
      testVectorVLA -  Tiempo transcurrido: 10.660610 segundos
      aldo@ao-linux-nb:~/blogtest$ g++ -O2 vlacpp.cpp -o vlacpp
      aldo@ao-linux-nb:~/blogtest$ ./vlacpp 2000
      testVLA       -  Tiempo transcurrido: 0.768702 segundos
      testMallocVLA -  Tiempo transcurrido: 0.694418 segundos
      testStackFLA  -  Tiempo transcurrido: 0.682241 segundos
      testHeapFLA   -  Tiempo transcurrido: 0.694299 segundos
      testVectorVLA -  Tiempo transcurrido: 1.364321 segundos
      ¿Cómo se ha portado std::vector? Yo diría que las cifras hablan por sí solas... bien, vamos a ponerlo en una forma diplomática: digamos que tenemos dos noticias, una buena y una mala:
      • la buena noticia es que el C++ es tan eficiente como el C (y sobre esto no tenía ninguna duda), de hecho el nuestro programa C transformado en C++ obtiene (en los primeros cuatro test) el mismo rendimiento (¡ir a controlar ahi, si no me creéis, eh!).
      • la mala noticia es que el C++ es tan eficiente como el C, pero sólo si lo usas como el C, entonces nada de STL y parafernalias varias.
      (...Abro otro paréntesis: por supuesto, la mala noticia anterior no se deriva sólo del simple test propuesto en este post: se deriva de años y años de observaciones y uso intensivo de los dos lenguajes, solo faltaría. Cierro el paréntesis...).

      Sin entrar mucho en detalles (tal vez un día voy a escribir un post específico sobre el tema) os presento mi opinión: el lenguaje C++ es un gran lenguaje potente, eficiente y expresivo (¡es pariente cercano del C!), con el cual se puede escribir Software de alta calidad. Pero los mejores resultados (al menos en términos de rendimiento y fluidez del código) se obtienen mediante el uso por lo cual fue diseñado originalmente, es decir, como un C a objetos. El cambio que tuvo más tarde (desde cuando cayó en manos de los committee ISO) no me gusta y no me convence... pero, por suerte (y esto es importante), todavía se puede utilizar en su esencia, la que permite escribir a objetos utilizando un lenguaje (casi) igual al C (y esto corresponde a la buena noticia aqui arriba. Obviamente, si rendimiento y fluidez del código no se consideran factores importantes, entonces todas estas consideraciones pierden de significado...).

      Ah, un último punto para los que se han sorprendido por el código C++ (aquí arriba) que incluye un VLA: es una amable oferta de nuestro amado GCC (en su encarnación G++). Por lo tanto es una extensión del lenguaje proporcionada por el compilador, ya que los VLAs no forman parte del estándar C++ (incluso las últimas versiones C++11 y C++14).

      En el próximo posta, para cerrar el círculo, vamos a hablar de un pariente cercano de los VLAs, o sea de la función alloca(). ¿Será otro bueno, otro feo, u otro malo?

      ¡Hasta el próximo post!

      viernes, 19 de mayo de 2017

      El bueno, el feo y el VLA
      cómo usar los Variable Length Arrays en C - pt.1

      La referencia cinematográficas de este mes es ideal: un Variable Length Array (VLA para los amigos) sería perfecto para interpretar el malo en la obra maestra "El bueno, el feo y el malo" del legendario Sergio Leone. Y al final del(proximo) post resultará claro por qué.
      ...hola soy un VLA: comienza a preocuparte...
      Los VLAs son una cosa relativamente nueva del C: se introdujeron en C99, y son, al parecer, el sueño hecho realidad del mundo C: "¡Finalmente hay array con dimensiones variable! ¡Ah, si los hubiera tenido antes '99!". Vale, la idea es simple: con un VLA tipo podéis escribir cositas como estas:
      void myVla(
          int size1,
          int size2)
      {
          // mi VLA de int
          int ivla[size1];
      
          // hace algo con el VLA de int
          ...
      
          // mi VLA bidimensional de float
          float fvla[size1][size2]:
      
          // hace algo con el VLA bidimensional de float
          ...
      }
      
      ¿Fantastico, no? Demasiado bueno para ser verdad... pero habrá algunas contraindicaciones? Definitivamente no en el rendimiento: justo por eso he escrito un poco de código para poner a prueba las prestaciones de los VLAs respeto a las alternativas directas: array dinámicos (con malloc()) y array estáticos (en heap y stack). ¡vamos con el código!
      #include <stdio.h>
      #include <time.h>
      #include <stdlib.h>
      
      #define MYSIZE  1000000
      
      // variable dummy con el fin de evitar el vaciado total de las funciones usando GCC -O2
      int avoid_optimization;
      
      // prototipos locales
      void testVLA(int size);
      void testMallocVLA(int size);
      void testStackFLA(int dum);
      void testHeapFLA(int dum);
      void runTest(int iterations, void (*funcptr)(int), int size, const char *name);
      
      // función main()
      int main(int argc, char* argv[])
      {
          // test argumentos
          if (argc != 2) {
              // error args
              printf("%s: wrong arguments counts\n", argv[0]);
              printf("usage: %s vla iterations [e.g.: %s 10000]\n", argv[0], argv[0]);
              return EXIT_FAILURE;
          }
      
          // extrae iteraciones
          int iterations = atoi(argv[1]);
      
          // ejecuta test
          runTest(iterations, &testVLA, MYSIZE, "testVLA");
          runTest(iterations, &testMallocVLA, MYSIZE, "testMallocVLA");
          runTest(iterations, &testStackFLA, 0, "testStackFLA");
          runTest(iterations, &testHeapFLA, 0, "testHeapFLA");
      
          // sale
          return EXIT_SUCCESS;
      }
      
      // función runTest()
      void runTest(
          int        iterations,      // iteraciones del test
          void       (*funcptr)(int), // función de test
          int        size,            // size del array
          const char *name)           // nombre función de test
      {
          // lee start time
          clock_t t_start = clock();
      
          // ejecuta iteraciones de test
          for (int i = 0; i < iterations; i++)
              (*funcptr)(size);
      
          // lee end time y enseña el resultado
          clock_t t_end = clock();
          double t_passed = ((double)(t_end - t_start)) / CLOCKS_PER_SEC;
          printf("%-13s -  Tiempo transcurrido: %f segundos\n", name, t_passed);
      }
      
      // función testVLA()
      void testVLA(
          int size)       // size para VLA
      {
          int vla[size];
      
          // loop de test
          for (int i = 0; i < size; i++)
              vla[i] = i;
      
          // instrucción con el fin de evitar el vaciado total de las funciones usando GCC -O2
          avoid_optimization = vla[size / 2];
      }
      
      // función testMallocVLA()
      void testMallocVLA(
          int size)       // size para malloc()
      {
          int *mallocvla = malloc(size * sizeof(int));
      
          // loop de test
          for (int i = 0; i < size; i++)
              mallocvla[i] = i;
      
          // instrucción con el fin de evitar el vaciado total de las funciones usando GCC -O2
          avoid_optimization = mallocvla[size / 2];
      
          free(mallocvla);
      }
       
      // función testStackFLA()
      void testStackFLA(
          int dum)        // parámetro dummy
      {
          int stackfla[MYSIZE];
      
          // loop de test
          for (int i = 0; i < MYSIZE; i++)
              stackfla[i] = i;
      
          // instrucción con el fin de evitar el vaciado total de las funciones usando GCC -O2
          avoid_optimization = stackfla[size / 2];
      }
      
      // función testHeapFLA()
      int heapfla[MYSIZE];
      void testHeapFLA(
          int dum)        // parámetro dummy
      {
          // loop de test
          for (int i = 0; i < MYSIZE; i++)
              heapfla[i] = i;
      }
      
      Aquí, como siempre, corto-y-pego lo que siempre escribo después de mostrar el código (equipo que gana no se cambia...): Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciones y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.

      Así que: dado que es un test comparativo he escrito una función runTest() que llama n-iteraciones de la función a probar y cuenta el tiempo utilizado. el main() llama simplemente cuatro veces runTest(), una para cada función. Las cuatro funciones de test que he escrito prueban (como indicado por los nombres, por supuesto): un C99-VLA, un tradicional malloc-VLA, un Fixed-LA alocado en el stack, y un Fixed-LA alocado en el heap. Para cada test se utiliza un (gran) array-size de 1000000, y el número de iteraciones se decide arrancando la aplicación (esto es muy útil, como veremos más adelante).

      Hay que notar que runTest() utiliza un function pointer para arranar el test (hemos visto algo parecido hablando aquí de las callback): he utilizado la versión extendida de la declaración (void (*funcptr)(int) + llamada de la función con el operador &) pero os recuerdo que, por ejemplo, GCC también digiere fácilmente la declaración simplificada (void funcptr(int) + llamada sin el operador &). La versión extendida es, obviamente, más portátil. Y ya que estamos en el tema de los compiladores: aunque los VLAs (y los loop for (int...)) que utilizo el código de este mes se permiten sólo de C99 en adelante no es necesario (si se utiliza GCC) especificar el flag -std=c99 en compilación: las versiones recientes de GCC incluyen por defecto (al menos) también el C99 (además de las extensiones GNU C): si realmente queréis estar seguros de que lo que habéis escrito cumpla con un standard en particular tenéis que utilizar otros flag: por ejemplo, si queréis escribir utilizando sólo el C89, tenéis que añadir en la línea de compilación: -std=c89 -pedantic. Si estáis utilizando un GCC un poco viejo la compilación del ejemplo os dará warning y/o errores, y habrá que volver a compilar forzando la compatibilidad con el C99.

      Los resultados son los siguientes:
      aldo@ao-linux-nb:~/blogtest$ gcc vla.c -o vla
      aldo@ao-linux-nb:~/blogtest$ ./vla 1000
      testVLA       -  Tiempo transcurrido: 4.263985 segundos
      testMallocVLA -  Tiempo transcurrido: 3.641929 segundos
      testStackFLA  -  Tiempo transcurrido: 4.292963 segundos
      testHeapFLA   -  Tiempo transcurrido: 4.285660 segundos
      aldo@ao-linux-nb:~/blogtest$ gcc -O2 vla.c -o vla
      aldo@ao-linux-nb:~/blogtest$ ./vla 10000
      testVLA       -  Tiempo transcurrido: 0.767087 segundos
      testMallocVLA -  Tiempo transcurrido: 0.690925 segundos
      testStackFLA  -  Tiempo transcurrido: 0.678178 segundos
      testHeapFLA   -  Tiempo transcurrido: 0.687785 segundos
      Como se puede ver he ejecutado dos test con/sin optimización (flag GCC -O2) y, obviamente, ha sido muy útil el parámetro n-iteraciones de la aplicación, que me ha permitido encontrar un valor adapto para obtener resultados significativos y, al mismo tiempo, para evitar tiempos de ejecución bíblicos con la versión sin optimizaciones. ¿Cómo podemos comentar? Pues bien, ¡el VLA se porta muy bien con/sin optimizaciones! Y consigue, prácticamente, los mismos resultados de su competidor directo, el malloc-VLA, ¡y es más fácil de usar!

      Así que, volviendo al tema principal: ¡VLA aprobado!

      PERO...

      bueno, el porque del VLA malo os lo explicaré en el próximo post, y que sepáis que no todo lo que brilla es oro... y solo por hacer un pequeño spoiler sobre las consideraciones finales: ¡yo nunca utilizo los VLAs en el código que escribo!

      ¡Hasta el próximo post!