C23 es el estándar que finalmente dota al lenguaje C de características de expressividad y seguridad que antes dependían de extensiones de compilador o de macros complejas. La gran novedad es la transición hacia un modelo de tipos más robusto y la eliminación de la sintaxis “sucia” de los prefijos con guion bajo para palabras clave que ya eran parte del lenguaje, como static_assert, alignas o thread_local.
A nivel interno, C23 introduce nullptr, que deja de ser una macro para 0 o (void*)0 y se convierte en una constante con su propio tipo, nullptr_t. Esto elimina la ambigüedad que existía cuando el compilador debía decidir si un 0 era un entero o un puntero, algo crítico en la sobrecarga de funciones (aunque la sobrecarga es de C++) y en la claridad del código. Además, la llegada de constexpr permite definir constantes reales en tiempo de compilación, integrándose en el sistema de tipos y respetando el ámbito (scope), a diferencia de las directivas #define que son simples sustituciones textuales del preprocesador.
Si necesitas escribir código más genérico o robusto, typeof te permite obtener el tipo de una expresión en tiempo de compilación, permitiendo declarar variables que se ajusten automáticamente al tipo de un dato sin conocerlo de antemano. Para la programación de sistemas de alta criticidad, C23 incorpora <stdckdint.h>, que proporciona funciones de aritmética verificada como ckd_add, permitiéndote detectar desbordamientos (overflow) de forma estándar y eficiente. Si ignoras estas herramientas, como por ejemplo, no verificar el retorno de una operación aritmética protegida, el programa continuará con un valor erróneo, provocando un comportamiento indefinido que es casi imposible de rastrear en sistemas embebidos.
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdckdint.h>
/* C23 introduce atributos para mejorar la comunicación con el programador */
[[nodiscard]]
int safe_calculate_offset(int base, int step, int count) {
int total_offset;
/* Uso de aritmética verificada para evitar desbordamientos de enteros */
if (ckd_mul(&total_offset, step, count)) {
return -1; // Error: desbordamiento detectado
}
return base + total_offset;
}
/* constexpr: Constante real con tipo definido, no una macro */
constexpr int MAX_PACKET_SIZE = 1024;
/* Atributo para marcar funciones que ya no deberían usarse */
[[deprecated("Usa la versión con verificación de seguridad")]]
int old_unsafe_add(int a, int b) {
return a + b;
}
void process_data(int *data, size_t size) {
/* nullptr: Comparación segura y con tipo específico */
if (data == nullptr) {
return;
}
printf("Procesando %zu elementos...\n", size);
}
int main(void) {
int base_value = 10;
int step_value = 50;
int count_value = 20;
/* typeof: Obtiene el tipo de una expresión en tiempo de compilación */
typeof(base_value) local_copy = base_value;
/* static_assert: Validación de propiedades en tiempo de compilación */
static_assert(sizeof(int) >= 2, "El tipo int es demasiado pequeño");
int offset = safe_calculate_offset(base_value, step_value, count_value);
if (offset != -1) {
printf("Offset calculado: %d\n", offset);
process_data(&local_copy, (size_t)MAX_PACKET_SIZE);
} else {
fprintf(stderr, "Error: Desbordamiento de entero detectado.\n");
return 1;
}
/* Uso de la palabra clave bool (ahora es keyword en C23) */
bool is_ready = true;
if (is_ready) {
printf("Sistema listo.\n");
}
/* Ejemplo de uso de nullptr con un puntero nulo */
int *ptr = nullptr;
if (ptr == nullptr) {
printf("El puntero es nulo de forma segura.\n");
}
return 0;
}
Desglose del ejemplo
En el código anterior, observamos cómo la integración de las nuevas características mejora la robustez. La función safe_calculate_offset utiliza [[nodiscard]], lo que obliga al llamador a procesar su valor de retorno; de lo contrario, el compilador lanzará una advertencia. Dentro de esta función, ckd_mul realiza la multiplicación y, si el resultado excede el rango del tipo int, devuelve un valor distinto de cero, permitiéndonos manejar el error antes de que ocurra un desbordamiento silencioso.
La variable MAX_PACKET_SIZE definida con constexpr garantiza que el valor sea tratado como una constante de tipo int en todo el programa, evitando los problemas de ámbito y de tipos débiles asociados a #define. Al usar typeof(base_value), el compilador deduce que local_copy debe ser de tipo int, lo que facilita la escritura de código que sea independiente del tipo subyacente. Finalmente, la comparación data == nullptr es ahora semánticamente correcta, ya que nullptr tiene un tipo nullptr_t definido, lo que hace que la comprobación sea más explícita y segura que comparar contra un simple 0.
El error frecuente
Un error común al adoptar las nuevas capacidades de C23 es confiar en constexpr o static_assert para resolver problemas de lógica que aún requieren ejecución, o ignorar la semántica de [[nodiscard]] en funciones críticas.
// Error: Ignorar el retorno de una función con [[nodiscard]]
int calculate_critical_value(int input) {
// ... lógica compleja ...
return input * 2;
}
[[nodiscard]] int check_status() {
return 0; // 0 indica error en este contexto
}
int main() {
// El compilador emitirá una advertencia (warning) si se ignora
check_status(); // <--- Error: El valor de retorno se descarta
// Error con aritmética verificada:
int res;
ckd_add(&res, 2147483647, 1);
// Si no se comprueba el retorno de ckd_add, 'res' contiene un valor
// de desbordamiento y el programa continuará con datos corruptos.
printf("%d", res);
return 0;
}
Si utilizas ckd_add o ckd_mul y no evalúas su valor de retorno, estás invalidando completamente la protección que el estándar te ofrece. Herramientas como UBSan (Undefined Behavior Sanitizer) detectarán el desbordamiento de entero, pero el error lógico de ignorar el código de error de la función de aritmética verificada es un fallo de diseño que debe ser mitigado mediante la revisión de tipos y la atención a las advertencias del compilador.
N° 140