El nuevo estándar de C, C11

Este año que termina nos deja con la actualización de dos grandes lenguajes de programación: C++ (vimos sus cambios en un artículo de dos partes y C, que lanzó C11 hace apenas dos semanas, con no demasiadas novedades que tratan de estandarizar características que muchos compiladores incluyen ya desde hace tiempo, y añadir una serie de mejoras al manejo de threads o de operaciones atómicas, así como añadir más compatibilidad con C++ (y en específico con su nueva versión, C++11).

Veamos algunos de los cambios con más detenimiento...

Funciones sin retorno (_Noreturn)

Se añade un modificador nuevo que permite al compilador optimizar el código de una función en caso de que nosotros especifiquemos que nunca va a retornar al llamante.

#include <nostdreturn.h>

void noreturn crash() {
    printf("Uops! :D\n");
    exit(-1);
}

Podemos usarlo en funciones que típicamente tratan casos de error que finalizan el programa por ejemplo.

Aparte, se añade un nuevo fichero de cabeceras, nostdreturn.h, donde se define la palabra clave noreturn como alias de _Noreturn.

Eliminada la función gets

Se elimina finalmente la función gets de la librería de C.

Esta función lee desde la entrada estándar un número indeterminado de carácteres (hasta un fin de línea). El hecho de que no se pueda especificar el máximo provoca que sea muy sencillo provocar un desbordamiento del buffer donde se están guardando esos carácteres.

#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Para evitar los enormes problemas de seguridad que esto conlleva, en este nuevo estándar se elimina la función (ya en C99 estaba des-recomendada), y se aconseja el uso de la nueva función gets_s, que posee la misma cabecera, añadiendo un parámetro en el que se especifica el número máximo de carácteres a leer (menos 1, para el \0).

Mayor soporte de cadenas unicode

Al igual que en C++, y de una forma compatible con él, se añaden nuevos tipos de datos y aliases para manejar cadenas unicode. Se da soporte a tres representaciones distintas de unicode: UTF-8, UTF-16, UTF-32.

Hasta ahora estos formatos se representaban usualmente mediante los tipos char, unsigned short y unsigned long. El problema es que el tamaño de esos tipos de datos es dependiente de la plataforma y el compilador, por lo que no se podía hacer código portable.

Este nuevo estándar (al igual que el de C++), crea dos nuevos tipos de datos, llamados char16_t y char32_t, creados con el objetivo de representar carácteres en UTF-16 y UTF-32 respectivamente. UTF-8 seguirá usando un char normal. Además, se permite crear literales en las tres representaciones mencionadas marcando la cadena de carácteres con uno de los siguientes tres prefijos: (u8, u, U).

Por último, se añaden nuevas funciones en la librería estándar (almacenadas en uchar.h) para realizar conversiones entre los distintos formatos.

Si quieres más información sobre esto puedes consultar la primera parte del artículo sobre C++11.

Aserciones estáticas (static_assert)

Al igual que C++11, esta nueva versión de C añade una nueva palabra clave, static_assert, que permite añadir aserciones en el código que se ejecutan a nivel de compilador.

Las aserciones se encargan de comprobar condiciones que tienen que cumplirse a la hora de ejecutar una sección de código. Lo que hace especiales a estas aserciones es que no se ejecutan en tiempo de ejecución (cuando el programa es ejecutado por el usuario), si no que son comprobaciones realizadas en la fase de compilación (en una fase tardía donde los tipos son conocidos).

static_assert(sizeof(void*) >= 8, "No eres de 64-bit :P");

Este tipo de aserciones completa a las dos existentes anteriormente. La que se ejecuta con el preprocesador, #error, y la que se ejecuta en tiempo de ejecución, assert().

Funciones con comprobación de fronteras

El título de esta sección queda un poco críptico (no sabía muy bien como traducirlo), pero no es nada nuevo. El nuevo estándar de C añade una serie de funciones a la parte de su librería estándar encargada de manejar cadenas de carácteres. Estas nuevas funciones, cuyos nombres terminan mediante el sufijo _s, se diferencian de sus hermanas por comprobar los límites de los arrays usados como buffers antes de realizar las operaciones pertinentes.

char* strcat(char *s1, const char *s2);
errno_t strcat_s(char *s1, size_t n, const char *s2);

Como puede verse, la diferencia entre strcat y strcat_s es el segundo parámetro, que indica el número máximo de elementos que puede albergar el buffer de destino. Gracias a ese parámetro la función puede hacer más comprobaciones en tiempo de ejecución, comprobando que no se sobrepase el límite de memoria que tiene asignado el buffer de destino, por ejemplo.

Al igual que para strcat, se han añadido versiones con comprobación de fronteras para otras tantas funciones de la librería estándar.

Estructuras anónimas

Tras ser durante mucho tiempo soportadas en C++ se añade a este nuevo estándar el soporte de estructuras y uniones anónimas.

Estas estructuras son idénticas a las normales, salvando que no tienen un nombre mediante las que ser identificadas. Son muy útiles, por ejemplo, para crear código menos verboso a la hora de incorporar estructuras anidadas.

struct Persona {
    char *nombre;
    union {
        long dni;
        char *pasaporte;
    }
}

struct Persona p1;
p1.dni = 7987271;

Como puede verse en el fragmento de código anterior, al ser el unión anónimo, para acceder a sus campos no es preciso anteponer ningún nombre, dejando un código más claro y legible.

Manejo de threads

Este es posiblemente el cambio más grande que nos da este nuevo estándar. Se ha añadido el soporte a threads en la librería estándar, sin necesidad de usar librerías externas como hasta ahora con, por ejemplo, los threads POSIX (pthread.h).

Se ha añadido un nuevo fichero llamado threads.h en el que han incluído nuevos tipos de datos y funciones para ayudar a programar de forma concurrente. Se incluye el manejo de threads, así como de mutex y variables condicionales (para sincronización).

Dejamos el uso de esta librería para otro artículo separado, que hay mucho de lo que hablar... :)

Macros genéricas

Se añade algo con el mismo olor (salvando las distancias) a los templates de C++. Se pueden crear macros genéricas, que permitan seguir uno u otro camino con respecto al tipo de los parámetros recibidos.

#define cbrt(X) _Generic((X), long double: cbrtl, \
                            default: cbrt, \
                            float: cbrtf)(X)

Este es el típico ejemplo para demostrar el funcionamiento de estas macros. La familia de funciones cbrt se encarga de hallar la raíz cúbica de un número, existiendo tres distintas funciones dependiendo de si el parámetro pasado es un long, un int o un float. Gracias a las nuevas macros genéricas podemos crear una macro que traduzca la llamada a la función correcta dependiendo del tipo del parámetro pasado.

Y hasta aquí...

Y poco más que contaros en este artículo. No son todas las novedades que da este nuevo estándar, pero si que son las más importantes a mi modo de ver. Si investigais un poco más podréis ver como se añaden igualmente ciertas funciones para facilitar el alineamiento de datos, así como para finalizar de forma segura un programa, mejor manejo de números complejos, ...

¡Feliz año nuevo para todos! :D

Comentarios