30 de mayo de 2016

Estructuras de control.

El lenguaje de programación C incorpora tres estructuras de control:
  1. Estructura de control secuencial.
  2. Estructuras de control para la selección de sentencias.
  3. Estructuras de control para la repetición de sentencias.
   La estructura de control secuencial se encarga de que se ejecuten en orden y en secuencia (de ahí su nombre), las sentencias, operaciones y expresiones escritas en la gramática del lenguaje C, esto es, de izquierda a derecha y de arriba hacia abajo.

   ¿Qué pasa cuando en un programa tenemos que hacer una división por ejemplo? Para el caso de la división (módulo) puede presentarse un problema debido a la indeterminación latente en la división. El resultado de la división se indetermina  si el denominador es cero, entonces, ¿qué hacer en estos casos? Para prevenir este tipo de situaciones se necesita de una estructura de control que permita procesar o no un grupo de sentencias, en función de alguna determinada condición. A éste tipo de estructuras de control se les conoce como estructuras de control para la selección de sentencias.

   Las estructuras de selección y de repetición de sentencias se describen en otras entradas en las que se muestra el funcionamiento y uso de dichas estructuras de control.
while vs do-while.


28 de mayo de 2016

Consideraciones respecto a la E/S de procesos.

   Cuando un proceso se ejecuta, tiene asociados tres flujos (streams) de manera automática sin que se haga nada especial para su creación:
  1. Entrada estándar (stdin): asociado al teclado, pero puede ser redireccionado.
  2. Salida estándar (stdout): asociado a la pantalla, pero puede ser redireccionado.
  3. Error estándar (stderr): asociado a la pantalla y no puede ser redireccionado.

   El redireccionamiento se refiere a la capacidad de un proceso de tomar o enviar datos de, o hacia un archivo. Ésto quiere decir, que un programa no siempre lee sus datos desde el teclado, o que no siempre envía sus datos a la pantalla, sino que los datos pueden ser leídos de, o enviados a archivos, sin incluir explícitamente sentencias o funciones para la gestión de dichos archivos.

   Usando redireccionamiento, el proceso no se “enteraría” de que los datos que lee o escribe, no están siendo obtenidos desde el teclado o presentados en la pantalla respectivamente, ya que el mecanismo de redireccionamiento es transparente para los procesos, favoreciendo con ello su versatilidad.

   Por último, es importante mencionar que los flujos están estrechamente relacionados con los archivos, mismos que se trataran de manera más detallada en otra entrada.


 

14 de mayo de 2016

Un primer programa en C.

Bienvenido a C.

   Con base en la estructura general de un programa en C, este Primer Ejemplo  muestra ya algunos de esos elementos.

   La estructura de control principal que gobierna al paradigma de la programación estructurada y al lenguaje de programación C, es la estructura de control secuencial. La estructura de control secuencial, ejecuta o procesa las sentencias, instrucciones, expresiones, funciones, etc., en orden consecutivo, esto es, en la misma forma en que la mayoría de los habitantes de América Latina leemos: de izquierda a derecha y de arriba hacia abajo, por lo que el procesamiento de las líneas de los códigos de los ejemplos seguirán dicha regla.

   Las líneas 1-3 del ejemplo muestran el uso de comentarios. Los comentarios en C se inician con una diagonal seguida inmediatamente de un símbolo de asterisco (“/*”), y terminan con los mismos símbolos pero colocados en orden inverso, es decir:  “*/”.

   El uso de comentarios dentro de un programa permite esclarecer algunos de los aspectos poco claros, o no tan evidentes de nuestro código o algoritmo. Los comentarios ayudan también a especificar cierta fórmula,  una determinada expresión, o algún aspecto considerado durante la elaboración del algoritmo.

   Los comentarios deberían ayudar a hacer más claro y comprensible un programa. Un código bien escrito debe ser auto documentado, esto es, que el uso apropiado de identificadores, la claridad de expresiones, y el espaciado deberían ser suficientes para hacer clara y comprensible la intención del código, pero cuando valga la pena hacer alguna aclaración por las razones anteriormente expuestas o alguna otra que se me haya escapado, los comentarios serán una herramienta muy útil.

   Ahora bien, aunque no es una regla del lenguaje, sí constituye una buena práctica de programación el iniciar los programas con un comentario que describa tanto la funcionalidad del programa, como algunas otras características que se consideren relevantes para su comprensión. En el ejemplo citado se muestra, además de lo anterior, el nombre del autor del código (línea 2). Se recomienda ampliamente seguir esta práctica.

   La línea 4 es una directiva de inclusión. Le indica al compilador que incluya la biblioteca de entrada y salida estándar (stdio.h) misma que contiene, entre otras cosas, a la función printf que se utilizará en la línea 7. La biblioteca de entrada y salida estándar se incluye en la mayoría de los programas en C.

   La línea 6 define la función principal main( ). El punto de entrada de todo programa en C es main, si esta función no existe, no es posible generar un programa ejecutable. La línea 6 indica también que main regresará un valor entero, mismo que está estrechamente relacionado con la línea 9. Todas las funciones en C deben especificar el tipo de dato de retorno; el tipo de dato por omisión es int (entero).

   Por otro lado, la línea 7 muestra el uso más simple quizá de la función printf. La función printf, toma el argumento entre comillas (“ ”), y lo envía a la salida estándar, la cual está normalmente asociada con la pantalla. Observe el símbolo “\n”, a éste tipo de símbolos se les denomina secuencias de escape, y aunque es un símbolo compuesto por dos caracteres: “\” y “n”, se hace referencia a él como uno solo.

   Técnicamente hablando, el símbolo “\n” representa un avance de línea y un retorno de carro, en analogía al mecanismo que utilizaban las máquinas de escribir mecánicas.  El avance de línea y retorno de carro (\n) le indican al cursor que avance al siguiente renglón y se ponga al principio del mismo después de haber escrito: “Bienvenido a C!”. Las sentencias en C terminan con “;”.

   La penúltima línea del ejemplo (línea 9), regresa el valor cero (0) al invocador. Vale la pena aquí hacer una mención comúnmente pasada por alto: como se mencionó en la descripción de las etapas del proceso de programación, es común utilizar un IDE para la elaboración de programas en C, lo cual no debe llevar al lector a pensar que éste sea el proceso final, sino que es preciso saber que es sólo una fase de diseño y producción. Una vez que un programa ha sido probado, depurado, corregido y mejorado, se espera que trabaje como una aplicación más, independiente del entorno en el que se desarrolló. Ahora bien, no debe perderse de vista que las aplicaciones son ejecutadas por el Sistema Operativo (SO), cualquiera que éste sea, por lo que el valor de retorno se regresa al invocador de la función main, es decir, al SO y de ahí su importancia.

   Si el SO recibe como valor de retorno de la aplicación el valor cero, se entiende que la aplicación terminó normalmente y sin problemas; en otro caso, podría haber ocurrido algún tipo de error o situación anormal durante la ejecución de la aplicación. Supongamos que durante la ejecución de un programa, éste solicita memoria al SO y por alguna razón no se le concede; en este caso, dicho programa no podría continuar su ejecución y tendría que terminar de manera anormal, regresando un valor distinto de cero, pues bien, éste valor es precisamente el que le serviría al SO para saber que hubo un problema, identificar de qué tipo (en base al valor recibido), e iniciar un mecanismo de recolección de basura o de compactación de memoria entre otras muchas posibilidades, todo en función del valor de retorno procesado.

   Finalmente, si se observan las líneas 6 y 10, dichas líneas contienen, respectivamente, los símbolos “{” y “}”, los cuales se denominan delimitadores de bloque, y su función es la de agrupar declaraciones y sentencias dentro de una sentencia compuesta, o bloque (cuerpo) de la función. Los delimitadores de bloque también agrupan declaraciones y sentencias de estructuras de control y se mostrará en ejemplos posteriores.

   Los delimitadores de bloque pueden ser visualizados como el inicio (begin) y el fin (end) utilizado en la definición de algoritmos pero en realidad son más que eso, ya que definen un bloque de construcción y en su momento se hará hincapié en ello, por ahora, resultará de utilidad que se visualicen de esta manera.

   Una vez que se ha comprendido el código del ejemplo el paso siguiente sería compilarlo. El proceso de compilación y ejecución es muy dependiente de la forma de trabajo: utilizando un IDE o una compilación en línea.

   La salida de nuestro ejemplo muestra en la pantalla el mensaje: Bienvenido a C!, tal como se presenta en la siguiente figura.

Salida del ejemplo del primer programa en C.
 
    Considere ahora esta versión alternativa del Primer Ejemplo, el cual muestra los siguientes aspectos respecto del ejemplo anterior:
  1. Pueden utilizarse varias funciones printf para imprimir un mismo mensaje en la pantalla, si ésto tiene o no sentido, es otra cosa.
  2. Pueden ir más de una sentencia por renglón: observe que hay varios printf por línea, y el “;” separa y delimita las sentencias. Lo anterior obedece a estilo de programación y conveniencia, lo que no debe olvidar es que, aunque para la computadora sería lo mismo procesar todo el programa en una sola línea, para los humanos no lo es, ya que repercute en la legibilidad y por consiguiente, en la comprensión del programa. No olvide que se requieren programas que sean fáciles de entender no sólo para quien lo escribió, sino para cualquiera que conozca la sintaxis del lenguaje.
  3. El número de sentencias o funciones printf por línea es independiente de la lógica del programa.
  4. Si no se le indica explícitamente a la función printf que avance de línea y regrese el cursor al inicio, el cursor se queda en la posición siguiente respecto al último símbolo puesto en pantalla.
   Compare los dos programas de ejemplo. No olvide que dichos programas son lógicamente equivalentes pero diferentes, ya que su código es distinto. Compruebe y corrobore que ambos programas producen exactamente la misma salida.


13 de mayo de 2016

Estructura de un programa en C.

   Un programa estructurado es lo opuesto a un programa desordenado. Un programa estructurado es un programa con una distribución específica y un orden específico de las partes que lo componen, en donde dichas partes constituyen un conjunto de elementos relacionados pero independientes entre sí.

   Con lo anterior en mente, un programa estructurado escrito en el lenguaje de programación C, tiene la estructura general descrita en este ejemplo.

Las líneas 5, 6 y 7 no siempre ni necesariamente van en ese orden, ya que depende de las características específicas del programa que se esté realizando. La regla de oro que sigue el lenguaje C en este sentido es la siguiente: antes de usar algo, ese algo debe estar previamente definido, por lo que si una estructura (struct) requiere de una unión (union) por ejemplo, dicha unión deberá estar definida antes que la estructura.



Etapas del proceso de programación.

Diseño del algoritmo.
   Esta etapa es, por mucho, la más importante del proceso de programación, por lo que no es casualidad que sea la primera, ya que a partir de la descripción de un problema al cual se le quiera dar solución a través de un programa estructurado escrito en lenguaje C por ejemplo, se realiza un análisis inicial para identificar:
  • Los datos de entrada.
  • Los datos de salida.
  • El proceso general de solución del problema.
   El proceso general de solución deberá ser progresivamente detallado y refinado, de tal forma que dicho proceso, a través de su especificación, constituirá la solución algorítmica inicial para el problema planteado.

   La especificación algorítmica debe ser la primera etapa durante el proceso de creación de programas, y es, en más de un sentido, la piedra angular del proceso de programación.

Pruebas al algoritmo.
   El algoritmo propuesto en la etapa de diseño del algoritmo, debe ser probado con algunos casos básicos y clave, con la finalidad de determinar la congruencia y efectividad de su solución. Es una mala práctica de programación y de ingeniería de software en general, el posponer las pruebas hasta escribir el programa derivado del algoritmo.

   Las pruebas son una parte fundamental para la validación del algoritmo, y están estrechamente relacionadas con el proceso de diseño del algoritmo. Su descripción está fuera de los alcances de este texto, pero el lector debería asegurarse de que se conocen y aplican al menos los conceptos básicos de pruebas de algoritmos.

Transcripción del algoritmo a un lenguaje de programación (generación del código fuente).
   Esta etapa es básicamente el proceso de traducción del algoritmo a la sintaxis de un lenguaje de programación específico que, para el caso de los ejemplos utilizados será el lenguaje de programación C.

   Sólo en los casos de programas excepcionalmente simples y cuando también se posea un significativo nivel de experiencia, podría iniciarse el proceso en esta etapa. Para la edición del programa en código fuente, se puede usar cualquier editor de texto, siempre que el archivo se guarde como texto simple sin formato, pero el uso de un IDE apropiado se recomienda ampliamente desde esta etapa (CodeLite, Code::Blocks o ZinjaI por ejemplo).

   La extensión del archivo con el código fuente debería ser “.c”, ya que algunos compiladores, en función de la extensión del archivo a compilar, procesan un conjuntos de directivas o mecanismos acordes a lo que se espera contenga el archivo. Extensiones como “.c++” o “.cpp” sugieren que el archivo a compilar hace uso de características extendidas del lenguaje C (C++), o hacen que el compilador interprete algunas sentencias del archivo como propias del paradigma de programación orientado a objetos por ejemplo, o que el compilador analice el código en base a otra gramática distinta a la de C, generando así mensajes que pudieran confundir al programador.

   También es posible editar archivos que agrupen un conjunto de funciones que parezcan archivos de biblioteca personalizados. Técnicamente no lo son, pero es un estándar de facto el nombrarlos de esta manera.

Compilación.
   La etapa de compilación puede resumirse como el proceso de traducción de un programa escrito en un lenguaje artificial de alto nivel (código fuente), a un conjunto de instrucciones ejecutables por una computadora (programa en código objeto o binario).

   El proceso de compilación se escucha simple pero en no lo es. De hecho, el compilador de un lenguaje como C realiza al menos cuatro etapas:
  1. Pre procesamiento: antes de cualquier cosa, el pre procesador de C ejecuta las directivas de inclusión de archivos (#include), y de definición de tipos (#define), que son las más comunes pero no las únicas. La etapa de pre procesamiento se realiza con la finalidad de definir y establecer las dependencias necesarias indicadas por este tipo de directivas.
  2. Análisis léxico: es la primera fase del proceso de traducción, y su función es la de descomponer el programa fuente en elementos léxicos o símbolos denominados tokens, los cuales serán utilizados en una etapa posterior durante el proceso de traducción. La salida del analizador léxico es la entrada para el analizador sintáctico.
  3. Análisis sintáctico: este tipo de análisis se encarga, grosso modo, de verificar que las sentencias contenidas en el archivo fuente correspondan a la gramática del lenguaje C, es decir, que se cumplan con las reglas de estructura, escritura, y orden del lenguaje.
  4. Análisis semántico: el analizador semántico analiza el significado, sentido o interpretación, así como la intención de los símbolos, elementos léxicos, sentencias y expresiones de la gramática del lenguaje C.
   El resultado de estas cuatro etapas es, en el mejor de los casos, la traducción a código objeto del código fuente.

   Por otro lado, existe el escenario de la detección de algún error identificado en alguna de las etapas anteriormente mencionadas del proceso de compilación. En éste sentido, pueden identificarse dos tipos de errores:
  1. Error fatal: este tipo de errores no generan el código objeto derivado del proceso de traducción, debido a la detección de un problema grave durante el análisis del código fuente. Los errores fatales pueden ser por ejemplo: la omisión de un punto y coma, la falta o inconsistencia de paréntesis o delimitadores de bloque, elementos (tokens) no definidos, etc. En el argot computacional, es común denominar a este tipo de errores como: errores en tiempo de compilación.
  2. Error preventivo: los errores preventivos (warnings) sí producen código objeto, y su carácter es más bien informativo respecto a posibles problemas detectados por el compilador durante el proceso de traducción. La mayoría de este tipo de errores son del tipo semántico, por lo que en este sentido, es responsabilidad del compilador indicar los elementos que le parecieron sospechosos; sin embargo, es responsabilidad del programador validar o no estas advertencias. Algunos de estos errores, si no se corrigen, derivan en problemas durante la ejecución del programa, pero no necesariamente, y se les denomina: errores en tiempo de ejecución. Por otro lado, debe tenerse presente que no todos los errores en tiempo de ejecución son derivados de errores preventivos, la mayoría de los errores en tiempo de ejecución provienen de una lógica incorrecta.
Vinculación.
   El vinculador o enlazador (linker) es el responsable de unir las distintas partes que conforman un programa en C.

   Tome en cuenta que, como parte del proceso de compilación de la etapa anterior y asumiendo que el código fuente fue traducido con éxito en código objeto, se tienen, hasta ese momento, módulos relacionados pero independientes entre sí.

   Tome en cuenta que incluso para el programa más simple, es común hacer uso de funciones, constantes simbólicas, o tipos de datos definidos en alguna de las bibliotecas de funciones que utiliza el lenguaje, por lo que el enlazador es responsable de verificar la correspondencia y existencia de los módulos a los que se hace referencia en el programa fuente, así como de establecer los vínculos o relaciones correspondientes con dichos módulos para constituir y generar un módulo único y funcional, mismo que conformará el conjunto final de instrucciones ejecutables o programa ejecutable.

   En resumen, la función del enlazador es la de unir o vincular el código producido por el programador con el código al que se hace referencia en las bibliotecas de funciones, con la finalidad de generar un archivo ejecutable: el programa ejecutable.

Carga y ejecución.
   Cada programa ejecutable derivado de un programa en C, cuando se ejecuta o procesa en la computadora recibe la denominación de proceso. Un proceso es una instancia en ejecución de un programa.

   Puede decirse en general que un programa y un proceso son conceptos intercambiables indistintamente, pero formalmente obedecen a aspectos diferentes.
  • Un programa es un conjunto de instrucciones almacenadas en disco, listas para ser potencialmente ejecutadas como una tarea o aplicación por parte del sistema operativo.
  • Un proceso es ese mismo conjunto de instrucciones cargadas ya en la memoria principal de la computadora, mismas que están en posibilidad real de ser procesadas o ejecutadas por el microprocesador. Todas las aplicaciones, tareas o procesos que se están ejecutando en una computadora, deben estar en memoria total o parcialmente.

   El proceso responsable de trasladar (cargar) las instrucciones de un programa a la memoria, para que sea susceptible de ser ejecutado como proceso se llama: cargador (loader).


Depuración.

   La depuración es propia del proceso de diseño de un programa y es, de hecho, el complemento de la etapa de pruebas, ya que en esta fase, se prueban el conjunto de instrucciones escritas en lenguaje artificial, con la finalidad de verificar y validar que se cumplen los criterios y objetivos para los cuales fue diseñado el programa.

   Es común asociar esta etapa a la corrección de errores o problemas (bugs) detectados durante la ejecución del programa y, aunque si bien es cierto que en esta etapa es en donde se tratan de identificar las sentencias que originan algún tipo de problema, no debe perder de vista que las pruebas constituyen una parte fundamental del proceso de diseño y programación, de tal forma que constituyen una parte fundamental de la verificación y validación del programa desarrollado, por lo que realizar las pruebas del algoritmo hasta esta etapa, es una mala práctica de programación.

   Esta etapa incluye técnicas básicas de depuración como: la inclusión de puntos de ruptura (break points), ejecución paso a paso (trace), inspección de variables y expresiones (watch), ejecución de módulos completos (step over), y ejecución de módulos paso a paso (step into), entre otros.


 

12 de mayo de 2016

El proceso de programación.

   El proceso de diseño y creación de programas estructurados utilizando un lenguaje de programación, incluye al menos las siguientes etapas:
  1. Diseño del algoritmo.
  2. Pruebas al algoritmo.
  3. Transcripción del algoritmo a un lenguaje de programación (generación del código fuente).
  4. Compilación del código fuente.
  5. Vinculación.
  6. Carga y ejecución.
  7. Depuración.
   Las etapas 3 - 7 se gestionan de manera sumamente conveniente dentro de un IDE (Integrated Development Environment) y, si bien éste no es indispensable para el desarrollo de programas, resulta de mucha utilidad. La figura siguiente muestra la interacción que se da entre algunas de estas etapas con el disco y la memoria principal de una computadora.
Etapas del proceso de compilación.

   Un IDE es básicamente una herramienta que nos permite y facilita la gestión de las diferentes etapas del proceso de programación, mediante la combinación de un conjunto de teclas, o de la selección de algunas opciones de menús por ejemplo. También proporcionan un entorno de programación accesible y práctico, facilitando con ello las tareas de edición, compilación, vinculación de módulos, ejecución y depuración entre otras.

   El proceso descrito se base en lenguajes de programación cuyo código fuente debe ser compilado, es decir, traducido, por medio del compilador, a un código que pueda entender la computadora (código de máquina). El lenguaje de programación C, es un lenguaje tradicional y ampliamente difundido para la escritura de programas estructurados, y su uso implica de manera subyacente el proceso de programación anterior.


 

11 de mayo de 2016

To teach or not to teach...

Muy a propósito del tema, deseo compartir esta semblanza de un personaje sumamente importante para el mundo, no sólo de la computación sino en general, el Profesor Dennis MacAlistair Ritchie (dmr) a ocho años de su fallecimiento. Dennis Ritchie, el padre del lenguaje de Programación C y co-desarrollador del Sistema Operativo UNIX.



   El Paradigma de la Programación Estructurada surge en el siglo pasado (XX), ésta expresión hace ya referencia a algo ¿antiguo? Considero que es relativo: respecto a la vida promedio de una persona en la tierra, quizá sí; respecto al tiempo de vida del universo o de la tierra (para acotarlo severamente), no.

   Pero aún suponiendo que pese a lo relativo del tiempo se considerara antiguo, ¿es posible decir que todo lo antiguo es obsoleto? Pienso que una respuesta afirmativa sería sumamente radical. En mi modesta experiencia como programador y académico, considero que el Paradigma de la Programación Estructurada sigue siendo parte de la formación fundamental de cualquier profesional de la computación, ya que constituye no sólo un referente como modelo de programación tradicional y, en consecuencia, elemental para la cultura de cualquier profesionista o entusiasta del área, sino que existen múltiples aplicaciones y usos todavía vigentes del paradigma que hacen que su conocimiento sea no sólo necesario, sino indispensable.

   He participado en algunas renovaciones de planes de estudio de carreras relacionadas con la computación y mi punto de vista prevalece. Debo confesar que en más de una ocasión he reflexionado si mi posición obedece más a un tipo de atavismo o raigambre derivados de mi formación, o si tal vez me aferro a algo que aprendí en los inicios de mi profesión y que, como sucede con muchas otras cosas en las personas, me rehúso a abandonarlo como resultado de algún tipo de apego. Sin embargo, después de un diagnóstico y análisis concienzudo, invariablemente llego a la misma conclusión: el Paradigma de la Programación Estructurada sigue siendo necesario como modelo de programación, como referencia, como cultura y como valor agregado y curricular del programador.

   Adicionalmente, es importante considerar que podría ser que la tendencia actual de la pseudo educación de principios del siglo XXI impulse también a desconocer las orígenes de los actuales sistemas computacionales modernos, muchos de los cuales se derivan de sus ancestros implementados en el Paradigma de la Programación Estructurada y que aún incorporan módulos de uso específico basados en dicho modelo. En cualquier caso, a cualquier entusiasta de la programación no le vendrá mal el conocer la propuesta del enfoque de la programación estructurada, sino que por el contrario, obtendrá distintos beneficios al poderlo agregar a su repertorio de conocimientos de paradigmas, técnicas y modelos de programación.