Comportamiento indefinido en C++ (Undefined Behavior)

El estándar ISO del lenguaje C++ establece reglas sintácticas y semánticas que conducen, bajo un estricto cumplimiento de las mismas, a programas bien definidos. Ciertos aspectos y operaciones de la máquina abstracta del lenguaje son descritos como dependientes de la implementación (implementation-defined), como por ejemplo el tamaño de un entero sizeof(int). Hablamos aquí de los parámetros de la máquina abstracta, los cuales deben ser adecuadamente documentados por la implementación (véase el punto 4.1.1.2 de [1]).

Otros aspectos y operaciones, por su parte, se describen sin especificar (unspecified), como por ejemplo el orden de evaluación de los argumentos en una llamada a función. Ello introduce características no-deterministas en la máquina abstracta: una instancia de la máquina abstracta puede tener más de una ejecución posible para un programa e input dados. En tales casos, y siempre que sea posible, el estándar trata de definir el conjunto de comportamientos permitidos (punto 4.1.1.3 de [1]), si bien la implementación no está obligada a documentar el comportamiento seguido en cada caso.

En este artículo centraremos nuestra atención en las operaciones descritas bajo la etiqueta de comportamiento indefinido (undefined behavior o simplemente UB), las cuales conducen a programas incorrectos (punto 4.1.1.4 de [1]). Entre tales operaciones, podríamos destacar [2, 3]:

Range-v3: Una introducción a la biblioteca (Parte VII)

Este nuevo post de la serie dedicada a la biblioteca Range-v3 centra su atención en la vista cartesian_product, destinada a iterar productos cartesianos de rangos. Analizaremos el modo de lograr un grado de expresividad en código similar al facilitado por Python, comparando las diferencias de rendimiento con dicho lenguaje.

Programando con C++20 (Parte V): CPOs (customization point objects)

 Artículos de la serie:


Entendemos como punto de personalización (customization point) toda aquella operación dentro de una biblioteca que, de forma opcional u obligatoria, delegue su comportamiento en código proporcionado por el programador-usuario [1].

Así, consideremos la plantilla de función estándar std::swap, que proporciona la posibilidad de sobrescribir su comportamiento. En estándares de C++ previos a 2020, el modo de empleo tradicional de este punto de personalización en un contexto genérico consistiría en:

  1. Introducir una declaración using std::swap.
  2. Realizar una o varias llamadas sin calificar (unqualified calls) a la función swap.

Programación científica (VI): Instalación de Matplot++ y mp-units con Mingw-w64

Artículos de la serie:

Las bibliotecas Matplot++ para la visualización gráfica de datos [1] y mp-units para la manipulación y el análisis dimensional de unidades en tiempo de compilación [2] constituyen dos herramientas de enorme valor para la computación científica en C++.

Supongamos instalado un entorno de trabajo en MS Windows basado en MSYS2, CMake y el compilador Mingw-w64, tal y como se explica en una serie de post anterior. Dada la ausencia, a día de publicación de este post, de las mencionadas bibliotecas en el repositorio de paquetes de MSYS2 [3], analizaremos el procedimiento a seguir para su instalación.

Programación científica (V): Comprobando la ley de Zipf

Artículos de la serie:
Nube de palabras para los sonetos de Shakespeare
En este artículo nos plantearemos un interesante problema de análisis estadístico de textos. Consideremos una obra literaria de cierta extensión --obtenida, por ejemplo, a través del proyecto Gutenberg [1]. Deseamos:

  1. Crear un mapa que asocie a cada frecuencia de ocurrencia en el texto un listado de aquellas palabras que se repitan dicho número de veces. Para ello, deberemos tokenizar el texto adecuadamente.
  2. Imprimir en la terminal, en orden decreciente, las cinco mayores frecuencias registradas junto a sus listas de términos asociadas.
  3. Registrar el conjunto de valores (ranking, frequency) para las palabras distintas del texto. La primera entrada indicará la posición en el ranking de una palabra, mientras que la segunda entrada contendrá su frecuencia de ocurrencia en el texto. Por ejemplo, si "the" fuese el término más empleado con una frecuencia de uso de 1345, deberemos registrar el punto (1, 1345) ; si "of" fuese el segundo término en el ranking con una frecuencia de 703, registraríamos (2, 703), etcétera. Si dos o más términos compartieran una misma frecuencia de uso, les asignaríamos posiciones de ranking consecutivas y la misma frecuencia.

Proyecciones con Boost.HOF

La biblioteca header-only Boost.HOF [1], acrónimo de Higher Order Functions, constituye una útil herramienta de programación funcional en C++ que, como su nombre indica, opera con funciones de orden superior --es decir, funciones que toman otras funciones como entrada y/o que devuelven otra función como salida. La biblioteca proporciona múltiples utilidades para funciones y objetos función que buscan resolver de forma sencilla problemas que, tradicionalmente, han sido abarcados con técnicas de metaprogramación más complejas (piénsese, por ejemplo, en SFINAE).

En este post analizaremos la funcionalidad del adaptador de funciones proj [2], que aplica proyecciones unarias sobre los argumentos de otra función. Disponemos de dos sobrecargas en el espacio de nombres boost::hof:

   template<typename Projection, typename F>    constexpr auto proj(Projection p, F f) -> proj_adaptor<Projection, F>;    template<typename Projection>    constexpr auto proj(Projection p) -> proj_adaptor<Projection>;

donde se asume que el tipo F es ConstInvocable [3].

Programación concurrente (VIII): Ejercicio resuelto de paralelización

 Artículos de la serie:


Ejercicio resuelto de paralelización


Última actualización del artículo: 6 de septiembre de 2021.

Con el fin de poner en práctica algunas de las técnicas analizadas en los artículos anteriores de esta serie, nos plantearemos el problema de, dada la ruta (path) de un directorio/carpeta de archivos (directory), mostrar en consola:
  1. El número y tamaño total de los archivos contenidos en el directorio --incluyendo posibles subdirectorios--, desglosados por orden alfabético de extensión.
  2. El número total de subdirectorios.
Por supuesto, trataremos de aprovechar la concurrencia soportada por la implementación para acumular la información de forma rápida y eficiente.

Acerca de la serie 'Programación concurrente'

Última actualización del anuncio: 2 de septiembre de 2021.

La serie de artículos del blog centrada en la programación concurrente con C++, publicada originalmente en 2016, ha sido revisada y ampliada con el fin de dar cabida a nuevas funcionalidades introducidas por los estándares C++17 y C++20. En particular, sus textos y códigos han sido reescritos siempre que ha sido necesario con el fin de garantizar tanto el rigor de los contenidos como una narrativa coherente.

A los cinco primeros artículos de la serie se suman ahora tres nuevos posts centrados en los hilos std::jthread cooperativamente interrumpibles (C++20), la implementación de colas concurrentes y un ejercicio resuelto de paralelización en el contexto de la gestión de información sobre directorios:

  1. Introducción básica: clase std::thread y función std::async
  2. Paralelización de algoritmos
  3. Comprendiendo std::async en más detalle
  4. ¿Cómo funciona std::packaged_task?
  5. Mecanismo de exclusión mutua (mutex)
  6. std::jthread: hilo cooperativamente interrumpible
  7. Queue sincronizado
  8. Ejercicio resuelto de paralelización
La serie seguirá siendo extendida en el futuro a través de nuevos artículos.

Programación concurrente (VII): Queue sincronizado

  Artículos de la serie:


Queue sincronizado


Última actualización del post: 28 de agosto de 2021.

Detalle de 'The Queue at the Fish-shop'
(1944) por Evelyn M. Dunbar
https://en.wikipedia.org/wiki/Evelyn_Dunbar
En este artículo analizaremos la implementación segura bajo hilos (thread-safe) de una estructura de datos tipo cola (queue). Para ello, emplearemos dos primitivas de sincronización disponibles a partir del estándar C++11:
  • std::mutex, que permite proteger a una variable compartida del intento de acceso simultáneo por parte de múltiples hilos (véase el quinto artículo de esta serie para más detalles, así como [1]).
  • std::condition_variable, que permite bloquear uno o más hilos de ejecución hasta que otro hilo modifique una variable compartida y notifique dicha acción. Sus funciones miembro públicas notify_one y notify_all permiten desbloquear, respectivamente, uno solo de los hilos en espera o bien todos los hilos bloqueados [2].

Programando con C++20 (parte IV): std::jthread

Artículos de la serie 'Programando con C++20': Artículos de la serie 'Programación concurrente':
  1. Concepts
  2. Lambdas 'templatizadas'
  3. Funciones std::erase y std::erase_if
  4. std::jthread
  5. CPOs (customization point objects)
  1. Introducción básica: clase std::thread y función std::async
  2. Paralelización de algoritmos
  3. Comprendiendo std::async en más detalle
  4. ¿Cómo funciona std::packaged_task?
  5. Mecanismo de exclusión mutua (mutex)
  6. std::jthread: hilo cooperativamente interrumpible
  7. Queue sincronizado
  8. Ejercicio resuelto de paralelización

Última actualización del artículo: 31 de agosto de 2021.

El estándar C++20 proporciona un nuevo hilo cooperativamente interrumpible llamado std::jthread [1]. Éste dispone de la misma funcionalidad que la clase std::thread (véase el primer artículo de esta serie), pero añade las habilidades adicionales de:

  • Realizar su unión (join) automáticamente durante su destrucción.
  • Permitir que se solicite su cancelación.

Programación científica (IV): Ceros de una función

 Artículos de la serie:

  1. Representando el atractor de Lorenz con C++20, Boost.Numeric.Odeint y Dlib
  2. Análisis dimensional con mp-units, regresiones no-lineales con Dlib.Optimization y visualización de datos con Matplot++
  3. Rectas de mejor ajuste por mínimos cuadrados (mp-units, Matplot++)
  4. Ceros de una función
  5. Comprobando la ley de Zipf
  6. Instalación de Matplot++ y mp-units con Mingw-w64

En este post analizaremos la obtención del cero de una función F(x) = 0 empleando el conjunto de herramientas matemáticas Math Toolkit de la familia de bibliotecas Boost [1]. En primera aproximación, descartaremos la presencia de múltiples ceros. A modo de ejemplo, consideremos la denominada ecuación de Colebrook-White, que relaciona la pérdida de presión por rozamiento a lo largo de una tubería en términos de la velocidad media del flujo del fluido:


GCC 11.1

El martes 27 de abril fue publicada de forma oficial la nueva versión 11.1 de la famosa colección de compiladores GCC, proporcionando numerosas características del nuevo estándar ISO C++ 2020. Entre ellas destacaríamos la habilitación de conceptos, corrutinas y el nuevo sistema de módulos del lenguaje (mediante el flag -fmodules-ts). 

El compilador proporciona, asimismo, nuevas funcionalidades recogidas en C++17, en particular las funciones std::from_chars y std::to_chars para valores en coma flotante. Se incluyen también algunas características preliminares de C++23, como la función miembro pública contain() para la clase std::string o la eliminación de paréntesis () redundantes en expresiones lambda.

Pueden consultarse más detalles en: https://gcc.gnu.org/gcc-11/changes.html

Programación científica (II): Análisis dimensional con mp-units, regresiones no-lineales con Dlib.Optimization y visualización de datos con Matplot++

Artículos de la serie:
  1. Representando el atractor de Lorenz con C++20, Boost.Numeric.Odeint y Dlib
  2. Análisis dimensional con mp-units, regresiones no-lineales con Dlib.Optimization y visualización de datos con Matplot++
  3. Rectas de mejor ajuste por mínimos cuadrados (mp-units, Matplot++)
  4. Ceros de una función
  5. Comprobando la ley de Zipf
  6. Instalación de Matplot++ y mp-units con Mingw-w64

Introducción


Última actualización del post: 24 de octubre de 2021.

El desgraciado error de conversión de unidades que condujo en 1999 a la desintegración en la atmósfera marciana de la sonda Mars Climate Orbiter de la NASA [1] es un ejemplo tristemente famoso de la necesidad de garantizar que nuestros códigos sean dimensionalmente correctos.

En este post, continuación natural de un artículo previo, estudiaremos el empleo de la biblioteca de unidades mp-units [2] como medio para lograr un análisis dimensional en tiempo de compilación, sin que ello represente sobrecoste alguno en tiempo de ejecución. A modo de aplicación, el artículo analizará un experimento real de laboratorio conducente a la determinación de la aceleración de la gravedad terrestre mediante tiros parabólicos. Su tratamiento de datos requerirá el empleo del algoritmo iterativo de Levenberg-Marquardt facilitado por la biblioteca Dlib [3] para la resolución de problemas de mínimos cuadrados no-lineales. Se hará uso, asimismo, de las funcionalidades de representación de datos ofrecidas por la biblioteca Matplot++ [4].

Programación científica (I): Representando el atractor de Lorenz con C++20, Boost.Numeric.Odeint y Dlib

Artículos de la serie:

Introducción


Última actualización del artículo: 9 de noviembre de 2021.

El sistema de Lorenz es un sistema dinámico determinista 3-dimensional descrito por las siguientes ecuaciones diferenciales ordinarias (EDOs) no-lineales [1]:

dx/dt = σ(y - x)

dy/dt = x(ρ - z) - y

dz/dt = xy - βz

donde σ, ρ y β son parámetros reales positivos. Dada una posición inicial p0 = (x0, y0, z0∈ 3, el sistema seguirá una órbita en el espacio de fases parametrizada por el tiempo t ∈ [0, +∞).