Los peligros de devolver punteros

noviembre 3, 2009

 

Cuando escoges un lenguaje que deja en tus manos la gestión de memoria tienes que tener mucho cuidado con la manera en que gestionas los recursos que están a tu disposición. Si bien es cierto que puedes tener mucha flexibilidad a la hora de manejar la forma en que pasas los datos a tus funciones o métodos, esa flexibilidad se convierte en responsabilidad a la hora de liberar de forma correcta lo que en algún momento reclamas.
Por norma, siempre es buena idea que algún punto de nuestro programa se haga responsable de la memoria que el mismo pide, de esta manera sabrá como y cuando liberarlo. Por ejemplo, si una función necesita pasar un espacio de memoria a otra función para que escriba en ella, la mejor idea es que la función cree el espacio y se lo pase a la función de lectura ya creado, de tal maneras que cuando lo use, sea capaz de eliminarlo sin problemas.
void f1()
{
  ...
  char* buffer = new char[100];
  f2(buffer);
  ...
  delete [] buffer;
  ...
}
Uno de los diseños que pueden inducir a problemas es el hecho de que una función devuelva un puntero a una memoria de la que dicha función pierde la responsabilidad. Por ejemplo
char* f2()
{
  char* buffer = new char[100];
  ...
  return buffer;
}
Este diseño, puede ocasionar que la memoria de buffer nunca sea liberada, por ejemplo, si usamos la llamada como parametro de otra función, esa memoria nunca se liberará, o bien por que directamente se nos olvide liberar la memoria que no hemos reservado en la función original, por ejemplo:
void f1()
{
  int len = strlen(f2()); // la memoria de f2 no es accesible
}
En este ejemplo vemos como la memoria que f2() ha reservado, nunca se libera, pues no hay ninguna variable que la referencie y que podamos usar para liberarla.

 

Anuncios

¿Qué es la metaprogramación?

octubre 12, 2009
En los últimos meses he estado cada vez mas ligado a C++, realmente no me pilla de nuevas pero si es cierto que en los ultimos tiempos mi produccion de codigo ha sido casi enteramente en Java.
La vuelta a C++ no me ha sentado nada mal, de hecho, he agradecido el dejar de hacer las mismas típicas aplicaciones de “Altas/Bajas/Modificaciones/Consultas”
En el refresco de C++, pude tener acceso a un libro que me prestó un compañero de trabajo. El libro en cuestión es “Modern C++ Design: Generic Programming and Design Patterns Applied” (http://www.amazon.com/Modern-Design-Generic-Programming-Patterns/dp/0201704315) de Alexei Alexandrescu. De momento solo he leido algunas paginas pero me ha servido para descubrir un mundo que hasta ahora no había explorado, la metaprogramación.
Es difícil simplificar algo como la metaprogramación en una sola frase, en una primera impresión, y como su propia palabra indica, es la programación sobre la programación, o también se puede ver como programas que construyen programas. Uno de los principales objetivos de esta técnica es llevar a tiempo de compilación cálculos que habitualmente se hacen en tiempo de ejecución.
Un claro ejemplo podría ser la construcción de una tabla de resultados, por ejemplo, si en nuestro programa necesitamos hacer una operación costosa, digamos una función matemática que exija mucho tiempo de cálculo, en lugar de llamar a dicha función en tiempo de ejecución, crearemos un código cuya salida sea los valores de esa función evaluada para diferentes valores y que sea usable desde nuestro programa, por ejemplo como la inicialización de un array con dichos valores.
== Ejemplo clásico ==
double funcion_costosa (int valor)
{
/* … */
}
cout << funcion_costosa(3);
== Ejemplo con técnicas de metaprogramación ==
double[] valores = {1.7862, 1.7652, 1.7890, 1.7675,….}; // Código generado con metaprogramming
cout << valores[3];
Como se puede ver en el ejemplo, si la optimización es nuestro objetivo, cuanto vamos a ganar usando técnicas de metaprogramacion en nuestros programas.
Ahora la pregunta es…¿y como puedo hacer eso? Realmente hay muchas técnicas para conseguir generar código, depende de la plataforma donde nos encontremos. La mayoría de estas técnicas recaen en preprocesadores o en los propios compiladores, concretamente en C++ una de las maneras de conseguirlo es usando plantillas.
Si algo me ha descubierto el libro que mencionaba al principio de la entrada es principalmente la flexibilidad y la potencia que tienen los “templates” en C++.
Un ejemplo habitual de metaprogramación en C++ usando plantillas suele ser el calculo de una serie de números, como por ejemplo el factorial, la serie de fibonacci o como vemos en el siguiente ejemplo, el desarrollo de la serie para calcular el valor de la función trigonometrica “seno”

En los últimos meses he estado cada vez mas ligado a C++, realmente no me pilla de nuevas pero si es cierto que en los ultimos tiempos mi produccion de codigo ha sido casi enteramente en Java.

La vuelta a C++ no me ha sentado nada mal, de hecho, he agradecido el dejar de hacer las mismas típicas aplicaciones de “Altas/Bajas/Modificaciones/Consultas”

En el refresco de C++, pude tener acceso a un libro que me prestó un compañero de trabajo. El libro en cuestión es “Modern C++ Design: Generic Programming and Design Patterns Applied” de Alexei Alexandrescu. De momento solo he leido algunas paginas pero me ha servido para descubrir un mundo que hasta ahora no había explorado, la metaprogramación.

Es difícil simplificar algo como la metaprogramación en una sola frase, en una primera impresión, y como su propia palabra indica, es la programación sobre la programación, o también se puede ver como programas que construyen programas. Uno de los principales objetivos de esta técnica es llevar a tiempo de compilación cálculos que habitualmente se hacen en tiempo de ejecución.

Un claro ejemplo podría ser la construcción de una tabla de resultados, por ejemplo, si en nuestro programa necesitamos hacer una operación costosa, digamos una función matemática que exija mucho tiempo de cálculo, en lugar de llamar a dicha función en tiempo de ejecución, crearemos un código cuya salida sea los valores de esa función evaluada para diferentes valores y que sea usable desde nuestro programa, por ejemplo como la inicialización de un array con dichos valores.

== Ejemplo clásico ==

double funcion_costosa (int valor)
{
   /* ... */
}
cout << funcion_costosa(3);

== Ejemplo con técnicas de metaprogramación ==

double[] valores = {1.7862, 1.7652, 1.7890, 1.7675,....}; // Código generado con metaprogramming
cout << valores[3];

Como se puede ver en el ejemplo, si la optimización es nuestro objetivo, cuanto vamos a ganar usando técnicas de metaprogramacion en nuestros programas.

Ahora la pregunta es…¿y como puedo hacer eso? Realmente hay muchas técnicas para conseguir generar código, depende de la plataforma donde nos encontremos. La mayoría de estas técnicas recaen en preprocesadores o en los propios compiladores, concretamente en C++ una de las maneras de conseguirlo es usando plantillas, y basándonos en técnicas como la especialización parcial y la recursividad.

Si algo me ha descubierto el libro que mencionaba al principio de la entrada es principalmente la flexibilidad y la potencia que tienen los “templates” en C++.

Un ejemplo habitual de metaprogramación en C++ usando plantillas suele ser el calculo de una serie de números, como por ejemplo el factorial, la serie de fibonacci o como vemos en el siguiente ejemplo, el sumatorio de los N valores:

template <int N>
struct Sumatorio
{
enum { result = N + Sumatorio<N-1>::result };
};
template <>
struct Sumatorio<0>
{
enum { result = 0 };
};
int sumatorio(int n) {
 return (n==0)? 0 : (n + sumatorio(n-1));
}
...
cout << Sumatorio<10>::result
...

Al compilar este ejemplo, el compilador se encargará de realizar la sustitución recursiva, de tal manera que en el código compilado la expresion Sumatorio<10>::result sera sustituido por “55”. Como vemos, la diferencia con realizar la llamada a la función sumatorio() es que si usamos templates, en tiempo de ejecución no se utilizará ni un ciclo de computación en calcular dicho sumatorio.

Si bien es cierto que mecanismos como estos tienen algunos inconvenientes como que por ejemplo hacen el código mas complicado de entender, desde el punto de vista del programador entrañan un bonito reto a acometer, cuyo principal objetivo puede ser la optimización. En mi opinión estos mecanismos añaden un poco de “picante” a la, a veces, monótona labor de programación.