Es común que en un programa no sepamos de antemano cuántos datos vamos a insertar, ni siquiera al arrancar el programa, si no en el transcurso de la ejecución del mismo. Es por eso que necesitamos una estructura de datos que permita ir agregando de manera ordenada datos, y guardarlos en memoria. Para ello existen una serie de estructuras útiles, pero una de las más simples son las listas doblemente ligadas. En este tutorial mostraremos una implementación libre de este tipo de listas e invitamos a todos los principiantes a revisar la entrada en wikipedia sobre el tema para tener más elementos en esta lectura.

En el presente artículo presentamos nuestra implementación de estas estructuras de datos, para que las puedan usar en sus proyectos o estudiar. Mostraremos las partes básicas para su uso, así como una descripción básica del funcionamiento de una lista de este tipo. Si quieren usar el código fuente está disponible en github

Estas listas requieren una estructura base, que mantenga información sobre el primer nodo de la lista y el último. 

typedef struct dllist
{
    dlnode *start;
    dlnode *end;
    dl_comparator comp;
    dl_print print;
    dl_freefunc free_func;
}dllist;

Como vemos, mantenemos punteros al nodo de inicio y al de fin. También, para generalizar, se usan punteros a funciones, que permitirán al usuario definir funciones bascias cómo liberar memoria, comparar nodos e imprimir un nodo. Ahora bien, cada nodo tendrá un puntero al objeto que contiene, uno al nodo predecesor y otro al nodo posteior. Si el nodo es el primero su valor será NULL, al igual si es el último, su nodo posterior también será NULL. En la siguiente imágen se ilustra cómo es esto:

Tomado de wikipedia.

Para muchos estudiantes de programación es difícil ver la utilidad de un arreglo dinámico en un lenguaje de bajo nivel cómo lo es C. ¿Por qué no simplemente usar un arreglo estático para realizar estas operaciones? La respuesta es clara: no siempre conocemos el tamaño de los datos con los cuáles vamos a trabajar. En este artículo vamos a tratar un tema que demuestra este punto. Supongamos que queremos multiplicar dos matrices que no conocemos en el momente de escribir el programa. Vamos a usar dos archivos donde se encuentren descritas las matrices. Dado que no conocemos el tamaño de estas matrices no podemos crear arreglos con un tamaño fijo,  por lo que el programa reservará el espacio necesario para almacenar los datos obtenidos de los archivos, en tiempo de ejecución. Esto significa que al momento de que se ejecute del programa, este creará un arreglo con los datos obtenidos en ese momento. Veamos la lógica general de un programa así.

 Primero será necesario definir las variables necesarias para realizar nuestro cálculo. En este caso nos centraremos en las variables importantes. Son las declaraciones de punteros a estructuras matrix M1, M2 y M. M1 y M2 apuntarán a la memoria que contendrá las matrices que leeremos del archivo, mientras que M será el resultado final de la multiplicación.

    matrix *M1, *M2, *M;

 ¿Pero qué aspecto tiene la estructura matrix?

typedef struct matrix{
    int m, n;
    float **M;
} matrix;

Es común que utilicemos números enteros en nuestros programas, olividándonos de su representación binaria en memoria. En este programa se va a iterar bit por bit por un entero. El primer problema es que los enteros int en C cambian dependiendo de la arquitectura. Si bien es común que contenga 32 bits, esto no es completamente cierto. Para ellos se asumen que cada Char equivale a 8 bits (algo que no es necesariamente cierto) y a través de sizeof se logra identificar el tamaño de un int. Sup es la variable del límite superior o tamaño del entero. sup será el contador que se disminuirá, mientras se imprime bit por bit.

La programación paralela es una forma de computación donde varios cálculos son efectuados de manera simultánea, bajo el principio de que cada problema puede ser separado en partes para su tratamiento. Este tiempo de programación es válido para equipos que tienen procesadores con varios núcleos (multicore) y computadoras con varios procesadores de memoria compartida. Sin embargo, se ha agregado un nuevo nivel de dificultad a la programación, donde las sincronización entre las subtareas y las condiciones de carreras introducen nuevos tipos de errores en los programas y nuevos retos para realmente obtener buenos resultados en ciertos problemas. También es importante considerar la llamada ley de Amdahl donde se plantea que los programas paralelos tienen un l´ımite de mejoramiento marcado por las partes del mismo que no pueden ser paralelizados. Para implementar este tipo de programación se ha implementado librerías que facilitan el trabajo paralelo, como son POSIX Threads, OpenMP y MPI (Message Passing Interface). Sin embargo, también existen lenguajes de programación que implementan de manera automática la paralelización, pero con resultados limitados. Ejemplos de estos lenguajes de paralelizado implícito es Parallel Haskell. En este breve texto, trataremos el lenguaje de programación C con la librería de OpenMP. Presentaremos el API de OpenMP, así como ejemplos sencillos para que el lector pueda acercase a la programación paralela. Con ejemplos dos ejemplos más complejos presentamos también aplicaciones reales de la programación en paralelo para el mejoramiento de algoritmos conocidos. Para una introducción más detallada puedes consultar: Primeros pasos en multicore y programación paralela.

El tutorial puede ser de su agrado en PDF desde este link. Es totalmente gratis y puede ser distribuido de la manera que más les agrade. http://code.kiutz.com/paral/docu.pdf Espero sea de su agrado. 

Si van a usarlo en algún trabajo, les agradecería que lo citaran con la siguiente especificación:

  • Jesús Manuel Mager Hois. Programación Paralela.  FES Cuautitlán-UNAM, Cuautitlán México, marzo del 2013.

 


En muchos medios circula el rumor o la versión casi oficial que no es posible programar en la actualidad sistemas o programas con lenguaje C. Muchas personas lo atribuyen a la falta del paradigma Orientado a Objetos en C, cómo si el único paradigma válido fuese este. Por otro lado, otros creen que al no ser orientado a objetos C no puede desplegar ventanas. Nada más falso que lo antes afirmado. C hoy en día se disputa directamente con Java la supremacía como el lenguaje más importante de nuestro tiempo.

¿Pero porqué se sigue usando C? C Es el lenguaje perfecto para muchos, entre los que me incluyo. Es minimalista y carente de una gran API. Esto pone en la mano de los programadores la decisión de incluir cualquier librería de terceros o el implementar uno mismo las funciones, el bajo nivel del lenguaje nos permite a su vez controlar todos cada ciclo del CPU, sin dejar nada a la interpretación del compilador, y por último tenemos todo el poder libre, pero con un gran poder viene una gran responsabilidad. Programar en C nos enseña responsabilidad sobre nuestras acciones, como lo es la reserva de memoria, su liberación, el aprovechamiento al óptimo de los recursos y por supuesto poner atención en los punteros. 

Lo antes mencionado puede sonar difícil y lo es. Pero esto no quiere decir que no sea la herramienta correcta para muchos proyectos. Algunos podrán decir: "C es bueno para nivel kernel, pero para nivel usuario no sirve". Nada mas falso que esto. A continuación presentaré diez programas que están escritos en C y que demuestran que se puede escribir cualquier tipo de aplicación con el. ¿El resultado? Un programa optimizado, estable y elegante en su código. 

Si por otro lado estás buscando programas famosos escritos en C++, que combinan la el rendimiento del bajo nivel y las herramientas del alto nivel, te recomendamos leer este artículo.

Share This