1 de junio de 2021

Prólogo.

Estimado lector:

    En 1962 ya se conocían ampliamente los conceptos de análisis y diseño de algoritmos y programación de computadoras, tanto así que Donald E. Knuth comenzó su titánica labor de documentar, en varios volúmenes de texto conocidos en conjunto como The Art of Computer Programming, su experiencia como programador. Hoy, en pleno siglo XXI, los conceptos expuestos por Knuth siguen siendo válidos, aunque, definitivamente, la labor del programador de computadoras se ha vuelto demasiado demandante y compleja.

    En nuestros tiempos, los profesionales del desarrollo de sistemas de software no sólo deben conocer un lenguaje de programación, sino que deben ser capaces de analizar requerimientos, diseñar la arquitectura de dicho sistema e invertir una gran cantidad de tiempo en validar el producto. Desafortunadamente, las tecnologías, las metodologías y los lenguajes de programación han evolucionado tan rápidamente que han dejado rezagados a los estudiantes de la disciplina del software. Muchos egresados de las universidades no tienen muy claro el reto al que se enfrentan ni tienen habilidades sólidas de análisis y uso adecuado de paradigmas de programación.

    Actualmente existen muchos libros que tratan sobre cómo escribir programas utilizando el lenguaje C. También, existen varios libros dedicados a exponer la teoría de los paradigmas de programación (estructurada, funcional, orientada a objetos, etcétera). Además, existen algunos libros que enseñan al lector a utilizar adecuadamente alguno de estos paradigmas para diseñar algoritmos que se implementan utilizando un lenguaje específico. Sin embargo, existen pocos libros que enseñan un paradigma específico utilizando un lenguaje específico para estudiantes que hablan una lengua específica.

    El libro que me honro en presentar al lector, escrito por el maestro Ricardo Ruiz Rodríguez, no sólo es producto de la experiencia docente del autor, que en sí misma es invaluable, sino que se deriva de un anhelo sincero de presentar el enfoque de la programación estructurada a los estudiantes en las universidades de México de una manera clara y didáctica. El libro ilustra conceptos de la programación estructurada como estructuras de control, descomposición funcional, recursividad y estructuras de datos básicas. Además, el presente texto detalla cómo utilizar los conceptos anteriores utilizando el lenguaje C.

    Esperamos que el presente libro le sirva como guía en su interminable aprendizaje y que le sirva como apoyo en su ruta hacia la excelencia como ingeniero de software. En éste camino debe enfrentarse a innumerables retos y adquirir conocimientos cada vez más avanzados, y dichas tareas se hacen más sencillas cuando se dispone de fundamentos sólidos, los cuales deseamos que el libro ayude a construir. Sin más por el momento, le envío un cordial saludo.


Dr. Tomás Balderas Contreras
Software Engineer
Guadalajara Design Center, Intel.

10 de abril de 2020

Compilación de programas.

   Existen básicamente dos tipos de compiladores para el lenguaje de programación C:
  1. Propietarios: compiladores que requieren de licencia para su uso.
  2. Código abierto o software libre (open source).
   En esta entrada se describen brevemente los elementos básicos para compilar en línea de comandos programas escritos en el lenguaje C utilizando el compilador de GNU GCC. GNU es un acrónimo recursivo que significa GNU No es Unix (GNU is Not Unix), y es un proyecto iniciado por Richard Stallman con el objetivo de crear un sistema operativo completamente libre. Dependiendo de la plataforma de software (sistema operativo) de su preferencia, se recomienda ampliamente el uso de un entorno de desarrollo (IDE) para facilitar el diseño, elaboración y depuración de sus programas; seleccione aquel que más se ajuste a sus necesidades, interés y/o presupuesto.

El compilador GCC.
   El compilador GCC es software libre y lo distribuye la Free Software Foundation (FSF) bajo la licencia general pública GPL (por sus siglas en inglés). Originalmente GCC significaba GNU C Compiler (compilador GNU de C) porque sólo compilaba el lenguaje C; sin embargo, GCC fue posteriormente extendido para compilar también otros lenguajes de programación.

   GCC es parte del proyecto GNU y tiene varios objetivos, pero el principal es mejorar el compilador usado en todos los sistemas GNU.

   El desarrollo de GCC usa un entorno de desarrollo abierto y soporta distintos tipos de plataformas con la intención de asegurar que GCC y los sistemas GNU funcionen en diferentes arquitecturas y en diferentes entornos; pero sobre todo, para extender y mejorar las características de GCC.

Compilación de programas con GCC.
   Esta sección se centra en explicar los elementos básicos para una compilación en línea de comandos para sistemas de tipo GNU/Linux sin entrar en los detalles de los comandos necesarios para cambiarse de directorio, listar archivos, etcétera. El símbolo siguiente representa el símbolo del sistema al que se estará haciendo referencia de aquí en adelante:

      $

   Dependiendo de la distribución de Linux que utilice el símbolo del sistema podría ser distinto, pero estos son detalles irrelevantes a la compilación de programas en C, y tampoco se detallarán aquí. Tome en cuenta que las descripciones siguientes asumen que tiene instalada alguna versión del compilador de GCC, y que los detalles de instalación y configuración de rutas (paths) quedan fuera de los propósitos de esta entrada.

   Por otro lado, considere que para todos los ejemplos descritos se supondrá que tanto el programa fuente como el ejecutable se generarán en el directorio de trabajo; es decir, el directorio en donde se ejecuten los comandos.

Comprobando la instalación de GCC.
   Para compilar programas en C hay que usar el compilador de C; y para usar el compilador de C, éste debe estar instalado.

   La mayoría de los compiladores reportan sus mensajes en inglés, aunque existen algunos mecanismos e implementaciones que los reportan en español. El compilador que se utilizará reporta los mensajes en inglés y así se presentarán en los ejemplos. Para determinar si tiene instalado el compilador GCC en su sistema, escriba lo siguiente (el uso de minúsculas es importante; de hecho, asegúrese de escribir los ejemplos tal y como aparecen en el texto) en el símbolo del sistema:

      $ gcc
      gcc: no input files


   Si la salida que obtiene no es semejante a la anterior, podría deberse básicamente a dos cosas:
  1. No se tiene instalado el compilador GCC.
  2. Tiene instalado el compilador GCC pero las rutas (paths) de acceso están mal configuradas, o no cuenta con los permisos requeridos para la ejecución del compilador.
   En cualquiera de los dos casos anteriores, no se podrán compilar programas en C, y deberá corregir dicha situación antes de poder continuar.

Compilación básica.
   Una vez que se ha comprobado que se cuenta con el compilador GCC, lo siguiente es compilar un programa en C.

   El Ejemplo bienvenido.c muestra un programa bastante simple, el cual se utilizará para el siguiente ejemplo de compilación:

      $ gcc -Wall bienvenido.c
      $


   Aquí es importante mencionar algunas cosas:
  1. Note que la extensión del archivo es ".c", lo cual es importante debido a que le indica al compilador GCC el tipo de compilador a utilizar, en este caso: el compilador de C.
  2. La opción "-Wall" activa todos los avisos (warnings) más comunes y se recomienda utilizarla siempre.
  3. La impresión del símbolo del sistema ($) sin ningún mensaje adicional implica que la compilación fue exitosa (compilación limpia) y que no existe ningún tipo de problema: este es el mejor de los casos.
  4. Si existen errores se reportarán antes de la siguiente aparición del símbolo del sistema, y estarán precedidos al menos por el número de línea en donde se detectó el problema. Dichos errores tendrán que ser corregidos en algún editor de texto, se deberán guardar los cambios realizados, y se deberá repetir nuevamente la compilación del programa fuente hasta que no aparezcan más errores (como se describe en el punto anterior).
  5. La compilación exitosa (sin errores) de un programa en C genera un archivo ejecutable de nombre "a.out".

   La ejecución del programa anterior, junto con la salida del programa, se muestran a continuación:

 

      $ ./a.out
      Bienvenido a C!
      $


Ligado de bibliotecas.

   Algunos programas utilizan bibliotecas de funciones que tienen que ser explícitamente vinculadas a dichos programas. Un caso muy común, es el de la biblioteca de funciones math.h.

   Si su compilador no reporta nada, compile tal y como se ha descrito hasta ahora; sin embargo, si al compilar alguna función matemática, se reporta algo como lo siguiente:

 

       $ gcc -Wall raices.c
      /tmp/ccoqkd6n.o: In function `main':
      raices.c:(.text+0xfb): undefined reference to `sqrt'
      raices.c:(.text+0x137): undefined reference to `sqrt'
      collect2: ld returned 1 exit status
      $

tendrá que compilar su programa de la siguiente manera:


      $ gcc -Wall raices.c -lm
      $


   Lo anterior le indica al compilador GCC que vincule, ligue o asocie la biblioteca de funciones matemáticas (link math (lm)) al programa fuente raices.c.


Generación de ejecutables.

   Una vez que se ha escrito, compilado, corregido y depurado un programa en C, en muchas ocasiones resulta deseable tener una imagen ejecutable de dicho programa con un nombre más significativo y representativo que el de a.out. En dichas situaciones, utilice la opción -o del compilador GCC para generar la salida (output) en un archivo específico:


      $ gcc -Wall bienvenido.c -o bienvenido
      $


   La sentencia anterior le indica la compilador GCC que compile el archivo bienvenido.c y genere, si todo está bien, el ejecutable en el archivo bienvenido, de tal forma que la ejecución del programa cambiaría de:


      $ ./a.out
      Bienvenido a C!
      $


a:

      $ ./bienvenido
      Bienvenido a C!
      $



Redireccionamiento.

   Los programas de una computadora leen normalmente sus datos de la entrada estándar (habitualmente asociada al teclado), y escriben sus datos en la salida estándar (habitualmente asociada a la pantalla); sin embargo, en algunas ocasiones es deseable que los procesos obtengan sus datos desde archivos, o que la información y los datos que generan sean almacenados en un archivo.

   Un mecanismo básico para lograr lo anterior es el de redireccionamiento. Por medio del redireccionamiento, es posible hacer que un programa procese los datos de entrada desde un archivo, y/o hacer que los datos o la información que genere sean enviados a un archivo sin la necesidad de incluir instrucciones explícitas de gestión de archivos dentro del programa.


Redireccionamiento de la entrada estándar.

   Considere el Ejemplo 3.17 de la entrada Estructuras de Control Combinadas. Sus detalles no se analizarán aquí, sino que se utilizará como referencia para mostrar el redireccionamiento de la entrada estándar.

   Asumiendo que el programa fuente se llama cuentaVocales.c, la siguiente secuencia de comandos muestra los detalles desde su compilación, hasta el redireccionamiento de la entrada estándar:


      $ gcc -Wall cuentaVocales.c -o cuentaVocales
      $ ./cuentaVocales < archivo.txt


   El símbolo "< " le indica al sistema operativo que la entrada estándar no será el teclado, sino el archivo archivo.txt; es decir, los datos no se procesarán desde el teclado sino desde dicho archivo.

   Observe que tanto la información como los datos que genera el programa cuentaVocales seguirán siendo presentados en la salida estándar (pantalla), debido a que no se ha realizado ninguna modificación sobre ella; los detalles de su redireccionamiento se mencionan a continuación.


Redireccionamiento de la salida estándar.

   El redireccionamiento de la salida estándar es análogo al de la entrada estándar, con la diferencia del símbolo utilizado para el redireccionamiento: ">".

   Continuando con el Ejemplo 3.17, el redireccionamiento de su salida estándar sería:


      $ ./cuentaVocales > salida.txt
      $


donde el archivo salida.txt contendrá la salida del programa cuentaVocales, incluso los mensajes de tipo prompt que presente (como la línea 11 del Ejemplo 3.17).

   Note que el símbolo del sistema aparece de inmediato, debido a que no hay nada que reportar en la salida estándar (pantalla) debido a que ha sido redireccionada.

   Finalmente, es importante mencionar que es posible redireccionar al mismo tiempo tanto la entrada como la salida estándar, de tal forma que los datos sean leídos desde un archivo (archivo.txt), y almacenados en otro archivo (salida.txt) tal y como se muestra a continuación:


      $ ./cuentaVocales < archivo.txt > salida.txt
      $


   Los mecanismos de redireccionamiento del sistema operativo están estrechamente relacionados con los descriptores de archivo que administra; sin embargo, no es la intención de esta entrada profundizar en dichos detalles que, si bien son importantes, quedan fuera de los alcances y objetivos del blog.

   Por el momento, basta con que comprenda el funcionamiento básico de los mecanismos de redireccionamiento descritos brevemente con anterioridad.


Consideraciones adicionales.

   La compilación de programas en línea es una tarea interesante, pero tome en cuenta que también puede llegar a ser abrumadora, sobre todo para programadores con poca experiencia.

   Revise la ayuda en línea:


      $ gcc --help

y el manual:

      $ man gcc

para tener una mejor y más amplia idea de todas las opciones, posibilidades y modalidades que se tienen al compilar en línea con GCC.

   La recomendación es nuevamente en este sentido, apoyarse de algún entorno de desarrollo que facilite, tanto la tarea de compilación como la de vinculación de bibliotecas así como la depuración de programas; de tal forma que, cuando se tenga un mejor nivel de experiencia, se puedan explorar la potencialidad y las características, no sólo del compilador GCC, sino de otras útiles herramientas de desarrollo como gdb, make, etc.


16 de enero de 2020

Archivos.

   Los programas de ejemplo desarrollados en entradas anteriores han utilizado hasta ahora, a la memoria principal tanto para su ejecución como para el almacenamiento de los datos que administran; sin embargo, una vez que los procesos terminan su ejecución, los datos leídos, procesados y utilizados por ellos desaparecen, y en muchas situaciones es conveniente, tanto preservar los datos como obtener grandes cantidades de ellos como entrada para los procesos. Para estos casos, el manejo de archivos resulta un elemento clave.

Definición y conceptos.
   Un archivo es, de manera general, un conjunto de bytes relacionados entre sí y referidos por un nombre.

   Dependiendo del esquema de almacenamiento utilizado, el conjunto de bytes puede estar agrupado en sectores como en el caso de los discos duros por ejemplo. Los sectores pueden ser de distintos tamaños y dependen tanto de la geometría del disco, como de la configuración del Sistema Operativo.

   Los archivos son el esquema más conveniente de preservación de datos más allá de la vida de un proceso, y también sirven como medio de comunicación entre los procesos que se ejecutan en una computadora.

En la entrada Consideraciones respecto a la Entrada y Salida (E/S) de Procesos, se mencionaron los tres tipos de archivos o flujos asociados a los procesos: stdin, stdout y stderr. En las siguientes entradas se presentará cómo asociar más archivos, representados por su respectivo flujo, a sus programas.

Dennis MacAlistair Ritchie (1941-2001).