Saltar a contenido

Capítulo 14 — Temas Avanzados y Bajo Nivel

Objetivos de aprendizaje

  • Comprender el modelo de memoria y los tres tipos de comportamiento: definido, definido por la implementación e indefinido (UB).
  • Dominar strict aliasing, atributos del compilador y ASM embebido.
  • Conocer las técnicas de optimización, la interoperabilidad con otros lenguajes y las novedades de C23.

14.1–14.3 Modelo de memoria y comportamientos

El estándar clasifica el comportamiento de un programa en tres categorías que debes interiorizar:

Categoría Significado Ejemplo
Definido El estándar garantiza el resultado 2 + 2 == 4
Definido por la implementación Cada compilador documenta su elección tamaño de int, >> con signo
No especificado El compilador elige, sin documentar orden de evaluación de argumentos
Indefinido (UB) Todo puede pasar desbordamiento con signo, desreferenciar NULL, data race

El UB no es un error que «a veces funciona»

Ante UB, el compilador asume que nunca ocurre y optimiza en consecuencia. Esto produce los famosos «the compiler deleted my security check»: un chequeo de desbordamiento eliminado porque el desbordamiento era UB. La defensa: -fsanitize=undefined, -Wall -Wextra, y conocer la lista de UB.


14.4 Strict aliasing

El compilador asume que punteros de tipos incompatibles no apuntan al mismo objeto, y optimiza sobre esa base. Violarlo es UB:

float f = 1.0f;
int i = *(int *)&f;        // ❌ viola strict aliasing (UB)
int j; memcpy(&j, &f, sizeof j);   // ✔ forma correcta de "type punning"

char * y unsigned char * pueden aliasar cualquier cosa (excepción del estándar). Compila con -fstrict-aliasing -Wstrict-aliasing para auditarlo.


14.5–14.6 Atributos y ASM embebido

[[noreturn]] void fatal(void);          // C23: la función no retorna
__attribute__((always_inline)) static inline int f(void);   // GNU
__attribute__((aligned(64))) int v[16];

// ASM embebido (GNU), para intrínsecas de bajo nivel:
uint64_t rdtsc(void) {
    uint32_t lo, hi;
    __asm__ volatile ("rdtsc" : "=a"(lo), "=d"(hi));
    return ((uint64_t)hi << 32) | lo;
}

14.7 Optimización de código

  • Niveles -O2/-O3/-Os; LTO (link-time optimization, -flto).
  • Profile-Guided Optimization (PGO): compila, perfila con datos reales, recompila.
  • restrict (C99): promete que un puntero es el único acceso a su objeto, habilitando optimizaciones potentes.
  • Branch hints: __builtin_expect / [[likely]] (C23).

Mide, no adivines

«Premature optimization is the root of all evil» (Knuth). Perfila (cap. 29) antes de optimizar; el compilador moderno suele ser más listo que tú en lo micro.


14.8–14.9 Programación de sistemas e interoperabilidad

  • Llamadas al sistema directas (syscall), memoria mapeada (mmap).
  • FFI: C es el lingua franca de la interoperabilidad. Python (ctypes, CPython API), Rust (extern "C"), Go (cgo), Java (JNI/Panama) llaman a C. La clave es la ABI estable y el extern "C".

14.10 C23: novedades del estándar

Característica Descripción
true/false/bool Palabras clave nativas
nullptr Constante de puntero nulo con tipo propio
constexpr Constantes evaluadas en compilación
typeof/typeof_unqual Deducción de tipo
[[attributes]] Sintaxis estándar de atributos
_BitInt(N) Enteros de anchura arbitraria
#embed Incrustar archivos binarios
<stdckdint.h> Aritmética con comprobación de desbordamiento
Funciones () = (void) Fin de los prototipos vacíos K&R

Detalle completo en el Apéndice D.


14.11–14.12 Seguridad, pruebas y documentación

  • Hardening: -D_FORTIFY_SOURCE=2, -fstack-protector-strong, ASLR, PIE.
  • Pruebas: marcos como Unity, Check, criterion; fuzzing (cap. 30).
  • Documentación: Doxygen genera referencia a partir de comentarios /** */.

Conexión con la actualidad

El undefined behavior es el tema de bajo nivel más relevante de la C moderna: es la raíz tanto de optimizaciones agresivas como de vulnerabilidades. La comunidad debate activamente «domesticar» el UB — proyectos como «Friendly C», los modos -fwrapv/-ftrapv, y la propuesta de un C provenance model para punteros (incorporada en parte a C23 mediante un Technical Specification). La ABI estable de C es lo que hace posible el ecosistema políglota de 2025: el FFI de Rust, el nuevo Project Panama de Java y el WASM Component Model se apoyan, en última instancia, en la interfaz de C. Y C23, con constexpr, nullptr y #embed, demuestra que el comité ISO sigue modernizando el lenguaje sin romper su modelo de bajo nivel.


Ejercicios

Ejercicio 14.1 — Caza el UB ★★★

Te damos 5 fragmentos con UB sutil (desbordamiento con signo, aliasing, lectura sin inicializar, índice fuera de rango, orden de evaluación). Identifícalos y confírmalos con UBSan.

Ejercicio 14.2 — type punning correcto ★★

Convierte un float a sus bits con memcpy y con union. Razona por qué el cast de puntero es UB.

Ejercicio 14.3 — restrict ★★★

Escribe void saxpy(int n, float a, float *restrict x, float *restrict y) y compara el ensamblador generado con y sin restrict.

Ejercicio 14.4 — Moderniza a C23 ★★★

Toma un módulo de capítulos previos y moderniza NULLnullptr, #define constantes → constexpr, y añade [[nodiscard]] donde proceda.


Referencias

  • ISO/IEC 9899:2024 (C23), borrador N3220.
  • What Every C Programmer Should Know About Undefined Behavior (LLVM Project blog, 3 partes).
  • Chris Lattner et al., serie sobre UB y optimización.
  • Computer Systems: A Programmer's Perspective (Bryant & O'Hallaron).