24 de octubre de 2007

Error Confuso con la Memoria

Lo peor que le puede pasar a un programador un viernes por la tarde es encontrar un nuevo error en el código en el que este trabajando. Si tiene suerte, lo podrá resolver en poco tiempo y marchara a casa tranquilo. Si no tiene tanta suerte, ese viernes saldrá tarde. Y si no tiene nada de suerte, además de salir tarde un viernes, dormirá mal ese día, y seguramente vaya el sábado a intentarlo arreglar.

Un viernes me toco a mi echarme a temblar. Faltaba media hora para que me marchase y se me acerca un compañero programador:

el otro: Oye, que me peta el programa en tu código.

yo: Bueno, vamos a ver que pasa. ¿Y en que parte de mi código?¿Por donde se iba ejecutando el programa?

el otro: Bueno.. er..esto.. en una función nueva que estoy haciendo. - Yo empezaba a ver un hueco por el que librarme.- Es que en algún lugar al principio de mi función peta sin más y el código que muestra en la pila de llamadas es una función tuya.

yo: Pero, lo que es mi función... Ni siquiera la llamas desde tu código.

el otro: Pues no. - Vamos, con un hueco así se libra hasta el más torpe.

yo: Vale. Pues algo haces en tu código, que corrompe la memoria de la pila de llamadas y después de petar casualmente se cree que el código causante del fallo es el mio.

el otro: Bueno, si. Puede ser eso. Lo mirare.

De buena me libre. Como tenía tiempo, y no tenía ninguna atadura moral en caso de no encontrar nada, me puse a mirar su código.

void FunctionF( void ){
MyStruct data;

FunctionG( loquesea );

// ...
}

El programa termina justo antes de entrar en la FunctionG por un acceso ilegal a memoria. Un primer vistazo, parece que los datos en la pila están corruptos. ¿Que es lo que ha podido causar la corrupción? Pues parece que el único sitio es la declaración de la variable data. Si vamos a ver que pinta tiene MyStruct, nos encontramos con lo siguiente.

const MAX_ARRAY = 128;

struct Element
{
int array1[ MAX_ARRAY ];
int array2[ MAX_ARRAY ];
int array3[ MAX_ARRAY ];
int array4[ MAX_ARRAY ];

unsigned int index1;
unsigned int index2;
unsigned int index3;
unsigned int index4;
};

const int NUM_ELEMENTS = 10;

struct MyStruct
{
Element elemens[ NUM_ELEMENTS ];
int posBest;
int posWort;
int posMean;
};

Un pequeño cálculo mental nos da que una variable de tipo MyStruct ocupa más de 20 kilobytes. Y esa es la solución al problema. La declaración de la variable data, llena todo el espacio de memoria dinámica en la pila, machacando en el proceso la pila de llamadas. Por lo que cualquier llamada a una función posterior causara una excepción.

Mi solución
void FunctionF( void )
{
MyStruct * data = new MyStruct;
assert( data );

// ...

delete data;
}

Bueno, pues la historia tuvo final feliz. Se cambio la inicialización de la variable y nos pudimos ir todos ese viernes con la conciencia tranquila a casa.

15 de octubre de 2007

Falta de Estilo

Vamos a ver el código a comentar esta semana. Es un trozo muy pequeño y apenas necesita de presentación.

    if ( condition1 )
{
if ( condition2 )
Table[Index] = eValue1;
else
Table[Index] = eValue2;
}
else
Table[Index] = eValue3;

¿Que es lo que crítico a este trozo de código?. La falta de estilo. La falta de estilo al poner las llaves de bloque. Y no me refiero solo a escribir código de una manera elegante y distinguida. Sino que el autor no sigue una manera práctica y distinguible de escribir código. Es más, no sigue el manual de estilo establecido para el proyecto al que pertenece este trozo de código. ¿Que ha decidido? ¿Poner llaves cuando hay más de una linea de código en el bloque? Si ni siquiera son necesarias las llaves. Esta manera de escribir los bloques if..then ..else tarde o temprano causa errores.

El manual de estilo son unas normas muy especificas que todo programador debería tener a la hora de trabajar en un proyecto. Ya sean normas auto impuestas o impuestas por el propio proyecto. Sirven principalmente para ayudar a la lectura y el entendimiento del código escrito y para evitar introducir errores.

Mi solución
    if ( condition1 )
{
if ( condition2 )
{
Table[Index] = eValue1;
}
else
{
Table[Index] = eValue2;
}
}
else
{
Table[Index] = eValue3;
}

El código alternativo que propongo es bien simple. Lo primero, seguir un manual de estilo o guía de escritura de código. Y en el proyecto al que pertenece el código anteror, lo que dice el manual es bien simple, usa siempre llaves después de un if o un else. De esta manera tienes un problema menos del que preocuparte, andar mirando si tienes o no que poner llaves, y dedicas el tiempo a lo que de verdad importa, programar. Y también evitas el error de poner sentencias fuera del if sin desearlo.

Y si el autor del código criticado estaba realmente preocupado por ahorrar espacio, por escribir en el mínimo número de lineas posibles, pues lo preferible hubiese sido usar la asignación condicional de C/C++. Siempre que el manual de estilo te lo permita. Es, quizás, hasta más legible.

    if ( condition1 )
{
Table[Index] = condition2 ? eValue1 : eValue2;
}
else
{
Table[Index] = eValue3;
}

8 de octubre de 2007

Poniendo Foto al Blog

Hola a todos,

Hoy vamos a añadir una imagen al blog, que no todo va a ser texto.



La imagen en concreto es de una excursión por el puerto de Lunada, entre Cantabria y Burgos.

3 de octubre de 2007

Variables en Haskell

Hoy nos olvidamos un poco de comentar código mal hecho y vamos a hablar del mejor lenguaje del mundo. Haskell.

Una de las caracteristicas que más se comentan de Haskell es que no usa variables destructivas, es decir, que una vez asignado el valor a una variable este permanece inmutable. Esto hace difícil programar código con datos que cambien de estado durante la ejecución del programa. Vamos a ver una manera de mantener y cambiar el estado de una variable. Para empezar, aqui teneis el código que usare. No voy a comentar el código, solo el uso que le voy a dar para crear una variable mutable. Comentare un poco por encima porque funciona al final.

import Data.IORef

incrementa::(Num a)=>IORef a->a->IO a
incrementa v n = do
val <- readIORef v
writeIORef v (val + n)
return (val + n)

creaContador::(Monad m, Num a)=>IORef a->a->m (IO a)
creaContador v n = return (incrementa v n)


Vamos a cargar el código anterior en una sesion interactiva de Haskell (hugs, ghci, ...etc). Lo primero que hacemos es crear un contenedor con el valor cero (fijaos como esquivo llamarlo variable).

ghci> v <- newIORef 0

Luego, usando la funcion creaContador que hemos definido anteriormente, vamos a crear una funcion que mantendrá una cuenta de las veces que se ha llamado. Que facil, ¿no?.

ghci> tick <- creaContenedor v 1

Ahora, si invocamos tick cuatro veces, nos devolvera sucesivamente los valores 1, 2, 3, y 4.

ghci> tick
1
ghci> tick
2
ghci> tick
3
ghci> cuenta <- tick
4
ghci> putStrLn $ "Numero de veces = " ++ (show cuenta)
Numero de veces = 4

Lo primero que tenemos que mirar es v, que es una referencia a una variable mutable. Es decir, es un contenedor para un valor. El valor con el que comienza es cero. Lo de hablar de un contenedor en vez de valor es la manera que tiene Haskell de seguir manteniendo inmutable v, v (el contenedor) permanece inmutable, lo que cambia es el contenido. A estos contenedores se les denominan Monads.

La función incrementa devuelve un valor de tipo IO a. IO también es un contenedor, el valor devuelto es un contenedor con contenido de tipo a. Es de tipo IO porque realiza una computación con la variable mutable, calcula un valor nuevo y lo mete de nuevo en el contenedor.

Y llegamos a la última parte, la función tick. No es una función. También es un contenedor (monad) cuyo contenido es una computación. Cada vez que invocamos tick, lo único que estamos haciendo es volviendo a ejecutar la computación que guarda. Por eso se incrementa el valor referenciado.

Y para terminar con Haskell por hoy. Fijaos en la diferencia entre llamar a creaContenedor y llamar a incrementa directamente, en el siguiente código no se guarda en tick la computación, sino el valor resultante de realizar la computación. Otro día hablamos de las funciones para tratar con monads (contenedores) como do, return y <-.

ghci> tick <- incrementa v 1 
1
ghci> tick
1
ghci> tick
1
ghci> tick
1