Cedro atlántico azul Cedro Español, English Sentido-Labs.com

Cedro es una extensión del lenguaje C que funciona como pre-procesador con nueve prestaciones:

  1. La macro pespunte x@ f(), g(y);f(x); g(x, y); (obras relacionadas).
  2. Devolución diferida de recursos auto ... o defer ... (obras relacionadas).
  3. Salida de bucles anidados break etiqueta; (obras relacionadas).
  4. Notación para porciones de ristras ristra[inicio..fin] (obras relacionadas).
  5. Macros de bloque #define { ... #define }.
  6. Macros de bucle #foreach { ... #foreach } (obras relacionadas).
  7. Inclusión binaria #embed "..." (obras relacionadas).
  8. Mejores literales numéricos (12'34 | 12_341234, 0b10100xA).

Para activarlo, el fichero fuente debe contener esta línea: #pragma Cedro 1.0
Si no, el fichero se copia directamente a la salida.

Esa línea puede contener ciertas opciones, por ejemplo #pragma Cedro 1.0 defer para activar el uso de defer en vez de auto.

El código fuente (licencia Apache 2.0) se encuentra en la biblioteca.
Para compilarlo, véase Compilar.
cedro sólo usa las funciones estándar C, cedrocc y cedro-new requieren POSIX.

Uso: cedro [opciones] <fichero.c>… cedro new <nombre> # Ejecuta: cedro-new <nombre> Para leer desde stdin, se pone - en vez de <fichero.c>. El resultado va a stdout, se puede compilar sin fichero intermedio: cedro fichero.c | cc -x c - -o fichero Es lo que hace el programa cedrocc: cedrocc -o fichero fichero.c Con cedrocc, las siguientes opciones son implícitas: --insert-line-directives Sólo se modifica el código tras la línea `#pragma Cedro 1.0`, que puede incluir ciertas opciones: `#pragma Cedro 1.0 defer` --apply-macros Aplica las macros: pespunte, diferido, etc. (implícito) --no-apply-macros No aplica las macros. --escape-ucn Encapsula los caracteres no-ASCII en identificadores. --no-escape-ucn No encapsula caracteres en identificadores. (implícito) --discard-comments Descarta los comentarios. --discard-space Descarta los espacios en blanco. --no-discard-comments No descarta los comentarios. (implícito) --no-discard-space No descarta los espacios. (implícito) --insert-line-directives Inserta directivas `#line`. --no-insert-line-directives No inserta directivas `#line`. (implícito) --embed-as-string=<límite> Usa cadenas literales en vez de octetos para ficheros menores que <límite>. Valor implícito: 0 --c99 Produce código fuente para C99. (implícito) Elimina los separadores de dígitos («'» | «_»), convierte literales binarios en hexadecimales («0b1010» → «0xA»), y expande `#embed`. No es un traductor entre distintas versiones de C. --c89 Produce código fuente para C89/C90. Por ahora es lo mismo que --c99. --c11 Produce código fuente para C11. Por ahora es lo mismo que --c99. --c17 Produce código fuente para C17. Por ahora es lo mismo que --c99. --c23 Produce código fuente para C23. Por ejemplo, mantiene los separadores de dígitos («'» | «_» → «'»), los literales binarios («0b1010» → «0b1010»), y no expande las directivas `#embed`. --print-markers Imprime los marcadores. --no-print-markers No imprime los marcadores. (implícito) --benchmark Realiza una medición de rendimiento. --validate=ref.c Compara el resultado con el fichero «ref.c» dado. No aplica las macros: para comparar el resultado de aplicar Cedro a un fichero, pase la salida a través de esta opción, por ejemplo: `cedro fichero.c | cedro - --validate=ref.c` --version Muestra la versión: 1.0 El «pragma» correspondiente es: `#pragma Cedro 1.0`

La opción --escape-ucn encapsula los caracteres Unicode® fuera del intervalo ASCII, cuando forman parte de un identificador, como nombres de caracteres universales C99 («C99 standard», página 65, «6.4.3 Universal character names»), lo que puede servir para compiladores más antiguos sin capacidad UTF-8 como GCC antes de la versión 10.

Para la documentación (en inglés) de la API, véase doc/api/index.html tras ejecutar make doc que necesita tener Doxygen instalado.

Compilar #compile

Lo más sencillo es cc -o bin/cedro src/cedro.c, pero es más conveniente usar make:

$ make help Objetivos disponibles: release: compilación optimizada, comprobaciones desactivadas, NDEBUG definido. → bin/cedro* debug: compilación para diagnóstico, comprobaciones activadas. → bin/cedro*-debug static: lo mismo que release, sólo que con enlace estático. → bin/cedro*-static doc: construye la documentación con Doxygen https://www.doxygen.org → doc/api/index.html test: construye tanto release como debug, y dispara la batería de pruebas. check: aplica varias herramientas de análisis estático: sparse: https://sparse.docs.kernel.org/ valgrind: https://valgrind.org/ gcc -fanalyzer: https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html clean: elimina el directorio bin/, y limpia también dentro de doc/.

cedrocc #cedrocc

El segundo ejecutable, cedrocc, permite usar Cedro como si fuera parte del compilador C.

Uso: cedrocc [opciones] <fichero.c> [<fichero2.o>…] Ejecuta Cedro en el primer nombre de fichero que acabe en «.c», y compila el resultado con «cc -x c - -x none» mas los otros argumentos. cedrocc -o fichero fichero.c cedro fichero.c | cc -x c - -o fichero Las opciones se pasan tal cual al compilador, excepto las que empiecen con «--cedro:…» que corresponden a opciones de cedro, por ejemplo «--cedro:escape-ucn» es como «cedro --escape-ucn». La siguiente opción es implícita: --cedro:insert-line-directives Algunas opciones de GCC activan opciones de Cedro automáticamente: --std=c90|c89|iso9899:1990|iso8999:199409 → --cedro:c90 --std=c99|c9x|iso9899:1999|iso9899:199x → --cedro:c99 --std=c11|c1x|iso9899:2011 → --cedro:c11 --std=c17|c18|iso9899:2017|iso9899:2018 → --cedro:c17 --std=c2x → --cedro:c23 Además, para cada `#include`, si encuentra el fichero lo lee y si encuentra `#pragma Cedro 1.0` lo procesa e inserta el resultado en lugar del `#include`. Se puede especificar el compilador, p.ej. `gcc`: CEDRO_CC='gcc -x c - -x none' cedrocc … Para depuración, esto escribe el código que iría entubado a `cc`, en `stdout`: CEDRO_CC='' cedrocc …

Si recibes un mensaje de error como «embedding a directive within macro arguments is not portable» (GCC) o «embedding a directive within macro arguments has undefined behavior» (clang), significa que usas Cedro con --insert-line-directives dentro de los parámetros de una macro. Puedes bien expandir el código dado a la macro manualmente, o evitar el --insert-line-directives reemplazando cedrocc -o fichero fichero.c con cedro fichero.c | cc -x c - -o fichero.

cedrocc hace otra cosa además de cedro fichero.c | cc -x c - -o fichero: para cada #include, si encuentra el fichero busca #pragma Cedro 1.0 y si lo encuentra (-I ...), procesa el fichero e inserta el resultado en lugar del #include. El motivo es poder compilar de una tacada programas que usen Cedro en varios ficheros, en vez de tener que transformar cada uno en ficheros temporales para compilarlos después.

cedro-new #cedro-new

Hay un tercer ejecutable, cedro-new, que produce un borrador de programa de manera similar a cargo new en Rust. cedro new … en realidad ejecuta cedro-new …. El contenido se produce a partir de la plantilla en el directorio template/, que se incluye en el ejecutable cedro-new al compilarlo.

Uso: cedro-new [opciones] <nombre> Crea un directorio llamado <nombre>/ con la plantilla. -h, --help Muestra este mensaje. -i, --interactive Pregunta por los nombres de programa y proyecto. Si no, se eligen a partir del nombre del directorio.

Al producir el borrador, se reemplazan ciertos patrones en Makefile, README.md, y todos los ficheros bajo src/:

La plantilla incluye varios borradores de proyectos, que se pueden activar en el Makefile generado:

Macro pespunte: @ #backstitch-macro

Hilvana un valor a través de una secuencia de llamadas de función, como primer parámetro para cada una.

Es una versión explícita de lo que hacen otros lenguajes de programación para implementar funciones miembro, y el resultado es un patrón habitual en bibliotecas en C.

Nota: el símbolo @ no se reconoce cuando se escribe \u0040, pero se convierte en @ en la salida. Esto sirve para encapsularlo al encadenar Cedro con otro pre-procesador que lo use.

objeto @ f(a), g(b); f(objeto, a); g(objeto, b);
&objeto @ f(a), g(b); f(&objeto, a); g(&objeto, b);
objeto.casilla @ f(a), g(b); f(objeto.casilla, a); g(objeto.casilla, b);
int x = (objeto @ f(a), g(b)); int x = (f(objeto, a), g(objeto, b));

Esto es el operador coma del C, lo mismo que

f(objeto, a); int x = g(objeto, b);
objeto @prefijo_... f(a), g(b); prefijo_f(objeto, a); prefijo_g(objeto, b);
objeto @..._sufijo f(a), g(b); f_sufijo(objeto, a); g_sufijo(objeto, b);
contexto_gráfico @nvg... BeginPath(), Rect(100,100, 120,30), Circle(120,120, 5), PathWinding(NVG_HOLE), FillColor(nvgRGBA(255,192,0,255)), Fill();
nvgBeginPath(contexto_gráfico); nvgRect(contexto_gráfico, 100,100, 120,30); nvgCircle(contexto_gráfico, 120,120, 5); nvgPathWinding(contexto_gráfico, NVG_HOLE); nvgFillColor(contexto_gráfico, nvgRGBA(255,192,0,255)); nvgFill(contexto_gráfico);

Para cada segmento separado por comas, si empieza con una de las piezas [, ++, --, ., ->, =, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=, o si no hay nada que parezca una llamada de función, el punto de inserción es el comienzo del segmento:

ristra_de_números @ [3]=44, [2]=11; ristra_de_números[3]=44; ristra_de_números[2]=11;
*ristra_de_números++ @ = 1, = 2; *ristra_de_números++ = 1; *ristra_de_números++ = 2;
punto_central_de_figura @ .x=44, .y=11; punto_central_de_figura.x=44; punto_central_de_figura.y=11;

Se pueden usar expresiones complejas como prefijos poniéndolas a la izquierda del @ y dejando los puntos suspensivos sin prefijo ni sufijo:

// ngx_http_sqlite_module.c#L802 (*chain->last)->buf->@ ... pos = u_str, last = u_str + ns.len, memory = 1;
// ngx_http_sqlite_module.c#L802 (*chain->last)->buf->pos = u_str; (*chain->last)->buf->last = u_str + ns.len; (*chain->last)->buf->memory = 1;

La parte de objeto se puede omitir, lo que sirve por ejemplo para añadir prefijos o sufijos a enumeraciones:

typedef enum { @PIEZA_... ESPACIO, PALABRA, NÚMERO } TipoDePieza; typedef enum { PIEZA_ESPACIO, PIEZA_PALABRA, PIEZA_NÚMERO } TipoDePieza;
// http://docs.libuv.org/en/v1.x/guide/threads.html#core-thread-operations // `liebre` y `tortuga` son funciones. int main() { int lon_pista = 10; @uv_thread_... t id_liebre, t id_tortuga, create(&id_liebre, liebre, &lon_pista), create(&id_tortuga, tortuga, &lon_pista), join(&id_liebre), join(&id_tortuga); return 0; }
// http://docs.libuv.org/en/v1.x/guide/threads.html#core-thread-operations // `liebre` y `tortuga` son funciones. int main() { int lon_pista = 10; uv_thread_t id_liebre; uv_thread_t id_tortuga; uv_thread_create(&id_liebre, liebre, &lon_pista); uv_thread_create(&id_tortuga, tortuga, &lon_pista); uv_thread_join(&id_liebre); uv_thread_join(&id_tortuga); return 0; }
función(a, @prefijo_... b, c) función(a, prefijo_b, prefijo_c)

La parte de los segmentos también se puede omitir para añadir bien un prefijo o un sufijo a un identificador:

Next(lector) @xmlTextReader...; xmlTextReaderNext(lector);
get(&vector, índice) @..._Byte_vec; get_Byte_vec(&vector, índice);
función(a, b @prefijo_..., c) función(a, prefijo_b, c)

Es un operador asociativo por la izquierda:

objeto @ f(a) @ g(b); g(f(objeto, a), b);
x @ uno() @ dos() @ tres() @ cuatro(); cuatro(tres(dos(uno(x))));

Buscando realizaciones anteriores de esta idea he encontrado magma (2014), donde se llama doto. Es una macro para el pre-procesador cmacro que tiene el inconveniente de necesitar el compilador Common Lisp SBCL además del compilador C.

Clojure también tiene una macro llamada doto que funciona de manera parecida, por ejemplo para hacer f₁(x); f₂(x); f₃(x);:

Magmadotomacro dotodoto(x) { f₁(); f₂(); f₃(); }
Clojuredotomacro doto(doto x f₁ f₂ f₃)
Cedro@macro pespuntex @ f₁(), f₂(), f₃()

Los lenguajes funcionales suelen tener un operador similar sin la capacidad de hilvanar un mismo valor a través de varias funciones. Por ejemplo, el equivalente de f₃(f₂(f₁(x))):

Shell|operador tuberíaecho x | f₁ | f₂ | f₃
Haskell&operador aplicación inversax & f₁ & f₂ & f₃
OCaml|>operador aplicación inversax |> f₁ |> f₂ |> f₃
Elixir|>operador tuberíax |> f₁ |> f₂ |> f₃
Clojure->macro hilvanado(-> x f₁ f₂ f₃)
Cedro@macro pespuntex @ f₁() @ f₂() @ f₃()

Ada 2005 introdujo una prestación llamada notación prefija [«prefixed-view notation»] que es más parecida al C++ ya que la función exacta que se ejecuta no se puede determinar sin conocer qué métodos están implementados para el tipo de objeto.

Devolución diferida de recursos: #deferred-resource-release

Mueve el código de devolución de una variable al final de su alcance, incluídos los puntos de salida break, continue, goto, return.

En C, los recursos deben devolverse al sistema explícitamente una vez no son necesarios, lo que generalmente ocurre bastante lejos de la parte donde se reservaron. Al pasar el tiempo y acumularse cambios en el programa, es fácil olvidar devolverlos en todos los casos o intentar devolver un recurso dos veces.

Otros lenguages de programación tienen mecanismos para devolución automática de recursos: C++ por ejemplo, usa funciones llamadas destructores que se ejecutan de manera implícita al salir del alcance de una variable.

El lenguaje Go introdujo una notación explícita llamada «defer» que pega mejor con el estilo del C. La primera diferencia es que en Go, todas las devoluciones ocurren al salir de la función, mientras que con Cedro las devoluciones ocurren al salir de cada bloque, como hacen los destructores en C++.

Hay más diferencias, como por ejemplo que en Go se puede usar para modificar el valor de retorno de la función, y que Cedro ni siquiera intenta tratar con longjmp(), exit(), thrd_exit() etc. porque sólo podría aplicar las acciones diferidas en la función actual, no en otras functiones que llamaran a ésta. Véase «A defer mechanism for C» (artículo académico publicado como PDF en la conferencia SAC’21) para una implementación a nivel de compilador que efectivamente trata con el longjmp() y con el desenrollado de la pila [«stack unwinding»].

En Cedro, la función de devolución se marca con la palabra clave C auto que no se necesita en código estándar C anterior al C23 porque es implícita y se puede reemplazar con signed ya que tiene el mismo efecto.

También es posible usar defer en vez de auto añadiendo la clave «defer» al «pragma»: #pragma Cedro 1.0 defer.

#pragma Cedro 1.0 … char* texto = malloc(cuenta + 1); if (!texto) return ENOMEM; auto free(texto); … if (nombre_de_fichero) { FILE* fichero = fopen(nombre_de_fichero, "w"); if (!fichero) return errno; auto fclose(fichero); fwrite(texto, sizeof(char), cuenta, fichero); … #pragma Cedro 1.0 defer … char* texto = malloc(cuenta + 1); if (!texto) return ENOMEM; defer free(texto); … if (nombre_de_fichero) { FILE* fichero = fopen(nombre_de_fichero, "w"); if (!fichero) return errno; defer fclose(fichero); fwrite(texto, sizeof(char), cuenta, fichero); …

En este ejemplo, hay un depósito de texto y un fichero que deben ser devueltos al sistema:

#include <stdio.h> #include <stdlib.h> #include <errno.h> #pragma Cedro 1.0 int repite_letra(char letra, size_t cuenta, char* nombre_de_fichero) { char* texto = malloc(cuenta + 1); if (!texto) return ENOMEM; auto free(texto); for (size_t i = 0; i < cuenta; ++i) { texto[i] = letra; } texto[cuenta] = 0; if (nombre_de_fichero) { FILE* fichero = fopen(nombre_de_fichero, "w"); if (!fichero) return errno; auto fclose(fichero); fwrite(texto, sizeof(char), cuenta, fichero); fputc('\n', file); } printf("Repetido %lu veces: %s\n", cuenta, texto); return 0; } int main(void) { return repite_letra('A', 6, "aaaaaa.txt"); }

#include <stdio.h> #include <stdlib.h> #include <errno.h> int repite_letra(char letra, size_t cuenta, char* nombre_de_fichero) { char* texto = malloc(cuenta + 1); if (!texto) return ENOMEM; for (size_t i = 0; i < cuenta; ++i) { texto[i] = letra; } texto[cuenta] = 0; if (nombre_de_fichero) { FILE* fichero = fopen(nombre_de_fichero, "w"); if (!fichero) { free(texto); return errno; } fwrite(texto, sizeof(char), cuenta, fichero); fputc('\n', file); fclose(fichero); } printf("Repetido %lu veces: %s\n", cuenta, texto); free(texto); return 0; } int main(void) { return repite_letra('A', 6, "aaaaaa.txt"); }

Compilación con GCC or clang, a la izquierda ejecutando explícitamente el compilador, y a la derecha usando cedrocc:

$ cedro repite.c | cc -o repite -x c - $ ./repite Repeated 6 times: AAAAAA $ cat aaaaaa.txt AAAAAA $ valgrind --leak-check=yes ./repeat … ==8795== HEAP SUMMARY: ==8795== in use at exit: 0 bytes in 0 blocks ==8795== total heap usage: 4 allocs, 4 frees, 5,599 bytes allocated ==8795== ==8795== All heap blocks were freed -- no leaks are possible $ cedrocc -o repite repite.c $ ./repite Repeated 6 times: AAAAAA $ cat aaaaaa.txt AAAAAA $ valgrind --leak-check=yes ./repeat … ==8795== HEAP SUMMARY: ==8795== in use at exit: 0 bytes in 0 blocks ==8795== total heap usage: 4 allocs, 4 frees, 5,599 bytes allocated ==8795== ==8795== All heap blocks were freed -- no leaks are possible

En este ejemplo adaptado de «Proposal for C2x, WG14 ​n2542, Defer Mechanism for C» pág. 40, los recursos devueltos son bloqueos giratorios [«spin locks»]: (la diferencia por supuesto es que en este caso las llamadas a spin_unlock() no se ejecutan tras el «panic»)

/* Adapted from example in n2542.pdf#40 */ #pragma Cedro 1.0 int f1(void) { puts("g called"); if (bad1()) { return 1; } spin_lock(&lock1); auto spin_unlock(&lock1); if (bad2()) { return 1; } spin_lock(&lock2); auto spin_unlock(&lock2); if (bad()) { return 1; } /* Access data protected by the spinlock then force a panic */ completed += 1; unforced(completed); return 0; } /* Adapted from example in n2542.pdf#40 */ int f1(void) { puts("g called"); if (bad1()) { return 1; } spin_lock(&lock1); if (bad2()) { spin_unlock(&lock1); return 1; } spin_lock(&lock2); if (bad()) { spin_unlock(&lock2); spin_unlock(&lock1); return 1; } /* Access data protected by the spinlock then force a panic */ completed += 1; unforced(completed); spin_unlock(&lock2); spin_unlock(&lock1); return 0; }

Andrew Kelley comparó la gestión de recursos entre C y su lenguaje de programación Zig en una presentación de 2019 titulada «The Road to Zig 1.0» a los 29:21s, y aquí he re-creado su ejemplo en C usando Cedro para producir la función tal cual la mostró, excepto que Cedro no sabe que el bucle for al final nunca termina así que añade devoluciones innecesarias de recursos tras él.

// Example retrofitted from C example by Andrew Kelley: // https://www.youtube.com/watch?v=Gv2I7qTux7g&t=1761s #pragma Cedro 1.0 int main(int argc, char **argv) { struct SoundIo *soundio = soundio_create(); if (!soundio) { fprintf(stderr, "out of memory\n"); return 1; } auto soundio_destroy(soundio); int err; if ((err = soundio_connect(soundio))) { fprintf(stderr, "unable to connect: %s\n", soundio_strerror(err)); return 1; } soundio_flush_events(soundio); int default_output_index = soundio_default_output_device_index(soundio); if (default_output_index < 0) { fprintf(stderr, "No output device\n"); return 1; } struct SoundIoDevice *device = soundio_get_output_device(soundio, default_output_index); if (!device) { fprintf(stderr, "out of memory\n"); return 1; } auto soundio_device_unref(device); struct SoundIoOutStream *outstream = soundio_outstream_create(device); if (!outstream) { fprintf(stderr, "out of memory\n"); return 1; } auto soundio_outstream_destroy(outstream); outstream->format = SoundIoFormatFloat32NE; outstream->write_callback = write_callback; if ((err = soundio_outstream_open(outstream))) { fprintf(stderr, "unable to open device: %s" "\n", soundio_strerror(err)); return 1; } if ((err = soundio_outstream_start(outstream))) { fprintf(stderr, "unable to start device: %s\n", soundio_strerror(err)); return 1; } for (;;) soundio_wait_events(soundio); } // Example retrofitted from C example by Andrew Kelley: // https://www.youtube.com/watch?v=Gv2I7qTux7g&t=1761s int main(int argc, char **argv) { struct SoundIo *soundio = soundio_create(); if (!soundio) { fprintf(stderr, "out of memory\n"); return 1; } int err; if ((err = soundio_connect(soundio))) { fprintf(stderr, "unable to connect: %s\n", soundio_strerror(err)); soundio_destroy(soundio); return 1; } soundio_flush_events(soundio); int default_output_index = soundio_default_output_device_index(soundio); if (default_output_index < 0) { fprintf(stderr, "No output device\n"); soundio_destroy(soundio); return 1; } struct SoundIoDevice *device = soundio_get_output_device(soundio, default_output_index); if (!device) { fprintf(stderr, "out of memory\n"); soundio_destroy(soundio); return 1; } struct SoundIoOutStream *outstream = soundio_outstream_create(device); if (!outstream) { fprintf(stderr, "out of memory\n"); soundio_device_unref(device); soundio_destroy(soundio); return 1; } outstream->format = SoundIoFormatFloat32NE; outstream->write_callback = write_callback; if ((err = soundio_outstream_open(outstream))) { fprintf(stderr, "unable to open device: %s" "\n", soundio_strerror(err)); soundio_outstream_destroy(outstream); soundio_device_unref(device); soundio_destroy(soundio); return 1; } if ((err = soundio_outstream_start(outstream))) { fprintf(stderr, "unable to start device: %s\n", soundio_strerror(err)); soundio_outstream_destroy(outstream); soundio_device_unref(device); soundio_destroy(soundio); return 1; } for (;;) soundio_wait_events(soundio); soundio_outstream_destroy(outstream); soundio_device_unref(device); soundio_destroy(soundio); }

Sin embargo, su ejemplo en Zig tuvo la ventaja injusta de devolver códigos de error en vez de imprimir los mensajes lo cual ocupa más espacio. Lo siguiente es una función en C que se ajusta más a la versión en Zig:

// Example retrofitted from Zig example by Andrew Kelley: // https://www.youtube.com/watch?v=Gv2I7qTux7g&t=1761s #pragma Cedro 1.0 int main(int argc, char **argv) { struct SoundIo *soundio = soundio_create(); if (!soundio) { return SoundIoErrorNoMem; } auto soundio_destroy(soundio); int err; if ((err = soundio_connect(soundio))) return err; soundio_flush_events(soundio); const int default_output_index = soundio_default_output_device_index(soundio); if (default_output_index < 0) return SoundIoErrorNoSuchDevice; const struct SoundIoDevice *device = soundio_get_output_device(soundio, default_output_index); if (!device) return SoundIoErrorNoMem; auto soundio_device_unref(device); const struct SoundIoOutStream *outstream = soundio_outstream_create(device); if (!outstream) return SoundIoErrorNoMem; auto soundio_outstream_destroy(outstream); outstream->format = SoundIoFormatFloat32NE; outstream->write_callback = write_callback; if ((err = soundio_outstream_open(outstream))) return err; if ((err = soundio_outstream_start(outstream))) return err; while (true) soundio_wait_events(soundio); } // Example retrofitted from Zig example by Andrew Kelley: // https://www.youtube.com/watch?v=Gv2I7qTux7g&t=1761s int main(int argc, char **argv) { struct SoundIo *soundio = soundio_create(); if (!soundio) { return SoundIoErrorNoMem; } int err; if ((err = soundio_connect(soundio))) { soundio_destroy(soundio); return err; } soundio_flush_events(soundio); const int default_output_index = soundio_default_output_device_index(soundio); if (default_output_index < 0) { soundio_destroy(soundio); return SoundIoErrorNoSuchDevice; } const struct SoundIoDevice *device = soundio_get_output_device(soundio, default_output_index); if (!device) { soundio_destroy(soundio); return SoundIoErrorNoMem; } const struct SoundIoOutStream *outstream = soundio_outstream_create(device); if (!outstream) { soundio_device_unref(device); soundio_destroy(soundio); return SoundIoErrorNoMem; } outstream->format = SoundIoFormatFloat32NE; outstream->write_callback = write_callback; if ((err = soundio_outstream_open(outstream))) { soundio_outstream_destroy(outstream); soundio_device_unref(device); soundio_destroy(soundio); return err; } if ((err = soundio_outstream_start(outstream))) { soundio_outstream_destroy(outstream); soundio_device_unref(device); soundio_destroy(soundio); return err; } while (true) soundio_wait_events(soundio); soundio_outstream_destroy(outstream); soundio_device_unref(device); soundio_destroy(soundio); }

La versión con Cedro se acerca mucho más, pero su argumento se mantiene porque la versión en C puro necesita mucho código repetido y es más frágil. Y por supuesto Zig tiene muchas otras prestaciones estupendas.

Aparte de la ya mencionada «A defer mechanism for C», hay macros que usan un bucle for como for (reserva e inicialización; condición; devolución) { acciones } [1] u otras técnicas [2].

[1] «P99 Scope-bound resource management with for-statements» del mismo autor (2010), «Would it be possible to create a scoped_lock implementation in C?» (2016), »C compatible scoped locks« (2021), «Modern C and What We Can Learn From It - Luca Sas [ ACCU 2021 ] 00:17:18», 2021
[2] «Would it be possible to create a scoped_lock implementation in C?» (2016), «libdefer: Go-style defer for C» (2016), «A Defer statement for C» (2020), «Go-like defer for C that works with most optimization flag combinations under GCC/Clang» (2021)

Compiladores como GCC y clang tienen características no estandarizadas para hacerlo, como el atributo de variables __cleanup__ (en inglés).

Cedro no tiene la limitación de que el código diferido tenga que ser una función: puede ser un bloque de código, con o sin condicionales, lo que permite por ejemplo emular el errdefer de Zig realizando acciones diferentes en caso de error:

char* reserva_bloque(size_t n, char** err_p) { char* resultado = malloc(n); auto if (*err_p) { free(resultado); resultado = NULL; } if (n > 10) { *err_p = "n es demasiado grande"; } return resultado; } char* reserva_bloque(size_t n, char** err_p) { char* resultado = malloc(n); if (n > 10) { *err_p = "n es demasiado grande"; } if (*err_p) { free(resultado); resultado = NULL; } return resultado; }

Salida de bucles anidados: #label-break

Convierte break etiqueta; o continue etiqueta; en goto etiqueta;. En C sólo es posible salir de un bucle cada vez al usar break, lo que también es un problema cuando la interrupción viene de un bloque switch.

#include <stdio.h> #include <stdlib.h> #pragma Cedro 1.0 int main(int argc, char* argv[]) { int x = 0, y = 0; for (x = 0; siguiente_x: x < 100; ++x) { for (y = 0; y < 100; ++y) { switch (x + y) { case 157: break encontrada_descomposición_del_número; case 11: x = 37; fprintf(stderr, "Saltamos de x=11 a x=%d\n", x); --x; continue siguiente_x; } } } encontrada_descomposición_del_número: if (x < 100 || y < 100) { fprintf(stderr, "Encontrada %d = %d + %d\n", x + y, x, y); } return 0; }

#include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[]) { int x = 0, y = 0; for (x = 0; x < 100; ++x) { for (y = 0; y < 100; ++y) { switch (x + y) { case 157: goto encontrada_descomposición_del_número; case 11: x = 37; fprintf(stderr, "Saltamos de x=11 a x=%d\n", x); --x; goto siguiente_x; } } siguiente_x:; } encontrada_descomposición_del_número: if (x < 100 || y < 100) { fprintf(stderr, "Encontrada %d = %d + %d\n", x + y, x, y); } return 0; }

La diferencia entre break …, continue …, y goto … está en las restricciones:

Es parte de la macro de devolución diferida de recursos:

#include <stdio.h> #include <stdlib.h> #pragma Cedro 1.0 int main(int argc, char* argv[]) { int x = 0, y = 0; char *nivel1 = malloc(1); auto free(nivel1); for (x = 0; siguiente_x: x < 100; ++x) { char *nivel2 = malloc(2); auto free(nivel2); for (y = 0; y < 100; ++y) { char *nivel3 = malloc(3); auto free(nivel3); switch (x + y) { case 157: break encontrada_descomposición_del_número; case 11: x = 37; fprintf(stderr, "Saltamos de x=11 a x=%d\n", x); --x; continue siguiente_x; } } } encontrada_descomposición_del_número: if (x < 100 || y < 100) { fprintf(stderr, "Encontrada %d = %d + %d\n", x + y, x, y); } return 0; }

#include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[]) { int x = 0, y = 0; char *nivel1 = malloc(1); for (x = 0; x < 100; ++x) { char *nivel2 = malloc(2); for (y = 0; y < 100; ++y) { char *nivel3 = malloc(3); switch (x + y) { case 157: free(nivel3); free(nivel2); goto encontrada_descomposición_del_número; case 11: x = 37; fprintf(stderr, "Saltamos de x=11 a x=%d\n", x); --x; free(nivel3); goto siguiente_x; } free(nivel3); } siguiente_x:; free(nivel2); } encontrada_descomposición_del_número: if (x < 100 || y < 100) { fprintf(stderr, "Encontrada %d = %d + %d\n", x + y, x, y); } free(nivel1); return 0; }

Usando goto en general no se puede garantizar que los recursos vayan a devolverse correctamente, pero con las restricciones al usar break … y continue … sí que funciona.

$ bin/cedrocc test/defer-label-break.c -std=c99 -pedantic-errors -Wall -fanalyzer -o /tmp/find-number-decomposition $ valgrind --leak-check=yes /tmp/find-number-decomposition … Saltamos de x=11 a x=37 Encontrada 157 = 58 + 99 ==1077== ==1077== HEAP SUMMARY: ==1077== in use at exit: 0 bytes in 0 blocks ==1077== total heap usage: 2,236 allocs, 2,236 frees, 6,683 bytes allocated ==1077== ==1077== All heap blocks were freed -- no leaks are possible ==1077== ==1077== For lists of detected and suppressed errors, rerun with: -s ==1077== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

El lenguaje de programación BLISS 11 fue el primero que introdujo etiquetas para su palabra clave leave (análogo al break del C) alrededor de 1974, y luego otros lenguajes como Java, Javascript, y Go hicieron lo mismo con continue y break.

Notación para porciones de ristras: #slice-notation

Convierte ristra[inicio..fin] en &ristra[inicio], &ristra[fin]. El valor ristra/puntero ristra puede ser simplemente un identificador o en general una expresión que, al evaluarse dos veces, no debe tener efecto secundario alguno, tal como las macros del preprocesador C estándar.

Podemos usarlo para mejorar el ejemplo de vectores de la sección de macros de bucles:

añade(&palabras, animales[0..2]); añade(&palabras, plantas[0..3]); añade(&palabras, animales[2..4]); añade(&palabras, &animales[0], &animales[2]); añade(&palabras, &plantas[0], &plantas[3]); añade(&palabras, &animales[2], &animales[4]);

El fin de la porción puede llevar un signo positivo para indicar que es una posición relativa al inicio de la porción: ristra[inicio..+fin] se convierte en &ristra[inicio], &ristra[inicio+fin]. En este caso, la advertencia sobre doble ejecución de efectos secundarios se aplica también a inicio además de a ristra.

añade(&palabras, animales[0..+2]); añade(&palabras, plantas[0..3]); añade(&palabras, animales[2..+2]); añade(&palabras, &animales[0], &animales[0+2]); añade(&palabras, &plantas[0], &plantas[3]); añade(&palabras, &animales[2], &animales[2+2]);

Si la ristra se compone de más de una pieza, se envuelve con paréntesis para asegurar que sea correcto.

añade(&palabras, (uint8_t*)animales[0..+2]); añade(&palabras, (uint8_t*)plantas[0..3]); añade(&palabras, (uint8_t*)animales[2..+2]); añade(&palabras, &((uint8_t*)animales)[0], &((uint8_t*)animales)[0+2]); añade(&palabras, &((uint8_t*)plantas)[0], &((uint8_t*)plantas)[3]); añade(&palabras, &((uint8_t*)animales)[2], &((uint8_t*)animales)[2+2]);

Se puede usar en inicializadores, en cuyo caso las llaves pueden omitirse:

#include <stdio.h> #pragma Cedro 1.0 typedef struct { const char* a; const char* b; } char_slice_t; const char* texto = "uno dos tres"; /** Extrae "dos" de `texto`. */ int main(void) { char_slice_t porción = texto[4..+3]; const char* cursor; for (cursor = porción.a; cursor != porción.b; ++cursor) { putc(*cursor, stderr); } putc('\n', stderr); }

#include <stdio.h> typedef struct { const char* a; const char* b; } char_slice_t; const char* texto = "uno dos tres"; /** Extrae "dos" de `texto`. */ int main(void) { char_slice_t porción = { &texto[4], &texto[4+3] }; const char* cursor; for (cursor = porción.a; cursor != porción.b; ++cursor) { putc(*cursor, stderr); } putc('\n', stderr); }

Este ejemplo completo muestra un letrero con texto deslizante:

#ifdef _WIN32 #include <windows.h> // Véase https://stackoverflow.com/a/3930716/ int sleep_ms(int ms) { Sleep(ms); return 0; } #else #define _XOPEN_SOURCE 500 #include <unistd.h> // Desfasado pero simple: int sleep_ms(int ms) { return usleep(ms*1000); } #endif #include <stdlib.h> #include <stdio.h> #include <string.h> #pragma Cedro 1.0 typedef char utf8_char[5]; // Como cadena C. void imprime_porción(const utf8_char* inicio, const utf8_char* final) { while (inicio < final) fputs(*inicio++, stdout); } int main(int argc, char* argv[]) { int tamaño_letrero = 8; if (argc < 2) { fprintf(stderr, "Uso: letrero <texto>\n", tamaño_letrero); exit(1); } const char separador[] = " *** "; size_t lon_octetos = strlen(argv[1]); size_t lon_separador = strlen(separador); char* m = malloc(lon_octetos + lon_separador); auto free(m); memcpy(m, argv[1], lon_octetos); memcpy(m + lon_octetos, separador, lon_separador); lon_octetos += lon_separador; // Extrae cada carácter codificado como UTF-8, // que necesita hasta 4 octetos + 1 terminador. utf8_char* mensaje = malloc(sizeof(utf8_char) * lon_octetos); auto free(mensaje); size_t lon = 0; for (size_t final = 0; final < lon_octetos;) { const char b = m[final]; size_t u; if (0xF0 == (b & 0xF8)) u = 4; else if (0xE0 == (b & 0xF0)) u = 3; else if (0xC0 == (b & 0xE0)) u = 2; else u = 1; if (final + u > lon_octetos) break; memcpy(&mensaje[lon], &m[final], u); mensaje[final][u] = '\0'; final += u; ++lon; } if (lon < 2) { fprintf(stderr, "texto de mensaje demasiado corto.\n"); exit(2); } else if (lon < tamaño_letrero) { tamaño_letrero = lon - 1; } for (;;) { for (int i = 0; i < lon; ++i) { int resto = i + tamaño_letrero > lon? i + tamaño_letrero - lon: 0; int visible = tamaño_letrero - resto; imprime_porción(mensaje[i .. +visible]); imprime_porción(mensaje[0 .. resto]); putc('\r', stdout); fflush(stdout); sleep_ms(300); } } return 0; }



#ifdef _WIN32 #include <windows.h> // Véase: https://stackoverflow.com/a/3930716/ int sleep_ms(int ms) { Sleep(ms); return 0; } #else #define _XOPEN_SOURCE 500 #include <unistd.h> // Desfasado pero simple: int sleep_ms(int ms) { return usleep(ms*1000); } #endif #include <stdlib.h> #include <stdio.h> #include <string.h> typedef char utf8_char[5]; // Como cadena C. void imprime_porción(const utf8_char* inicio, const utf8_char* final) { while (inicio < final) fputs(*inicio++, stdout); } int main(int argc, char* argv[]) { int tamaño_letrero = 8; if (argc < 2) { fprintf(stderr, "Uso: letrero <texto>\n", tamaño_letrero); exit(1); } const char separador[] = " *** "; size_t lon_octetos = strlen(argv[1]); size_t lon_separador = strlen(separador); char* m = malloc(lon_octetos + lon_separador); memcpy(m, argv[1], lon_octetos); memcpy(m + lon_octetos, separador, lon_separador); lon_octetos += lon_separador; // Extrae cada carácter codificado como UTF-8, // que necesita hasta 4 octetos + 1 terminador. utf8_char* mensaje = malloc(sizeof(utf8_char) * lon_octetos); size_t lon = 0; for (size_t final = 0; final < lon_octetos;) { const char b = m[final]; size_t u; if (0xF0 == (b & 0xF8)) u = 4; else if (0xE0 == (b & 0xF0)) u = 3; else if (0xC0 == (b & 0xE0)) u = 2; else u = 1; if (final + u > lon_octetos) break; memcpy(&mensaje[lon], &m[final], u); mensaje[final][u] = '\0'; final += u; ++lon; } if (lon < 2) { fprintf(stderr, "texto de mensaje demasiado corto.\n"); exit(2); } else if (lon < tamaño_letrero) { tamaño_letrero = lon - 1; } for (;;) { for (int i = 0; i < lon; ++i) { int resto = i + tamaño_letrero > lon? i + tamaño_letrero - lon: 0; int visible = tamaño_letrero - resto; imprime_porción(&mensaje[i], &mensaje[i+visible]); imprime_porción(&mensaje[0], &mensaje[resto]); putc('\r', stdout); fflush(stdout); sleep_ms(300); } } free(mensaje); free(m); return 0; }

La notación [a..b] para porciones de ristras se definió por primera vez en Algol 68 donde era una alternativa a la notación primaria [a:b], y ambas han sido adoptadas por otros lenguajes desde entonces. La forma [a..b] se usa en Ada, Perl, D, y Rust, por ejemplo.

Macros de bloque: #block-macros

Formatea una macro multi-línea en una sola línea.

Las macros en C deben escribirse todo en una línea, pero a veces hay que partirlas en varias pseudo-líneas y se hace tedioso y propenso a errores el mantener todos los escapes de nueva línea \.

Añadiendo llaves { o } justo tras #define podemos hacer que Cedro se encargue de eso por nosotros:

#define { macro(A, B, C) /// Versión de f() para el tipo A. f_##A(B, C) /// Sin punto y coma «;» al final. #define } int main(void) { int x = 1, y = 2; macro(int, x, y); // → f_int(x, y); } #define macro(A, B, C) \ /** Versión de f() para el tipo A. */ \ f_##A(B, C) /** Sin punto y coma «;» al final. */ \ /* End #define */ int main(void) { int x = 1, y = 2; macro(int, x, y); // → f_int(x, y); }

En casos como este («function-like macros»), al no haber punto y coma tras f_##A(B, C) herramientas tales como editores de texto (p.ej. Emacs) sangran [«indent»] el código incorrectamente.

La solución es dejarlo ahí para el editor, y añadirlo también tras #define } como #define }; lo que indica a Cedro que lo elimine de la definición.

#define { macro(A, B, C) /// Versión de f() para el tipo A. f_##A(B, C); /// Punto y coma «;» eliminado por Cedro. #define }; int main(void) { int x = 1, y = 2; macro(int, x, y); // → f_int(x, y); } #define macro(A, B, C) \ /** Versión de f() para el tipo A. */ \ f_##A(B, C) /** Punto y coma «;» eliminado por Cedro. */ \ /* End #define */ int main(void) { int x = 1, y = 2; macro(int, x, y); // → f_int(x, y); }

Las directrices de preprocesador no se permiten dentro de macros, de manera que no se puede usar #if, #include, etc.

Nota: la directiva debe empezar exactamente con #define { o #define }, ni más ni menos espacio entre #define y la llave { o }.

Macros de bucle: #loop-macros

Repite las líneas entre #foreach { ... y #foreach } sustituyendo las variables dadas. Esas líneas pueden contener definiciones de macro (#define) pero las variables del bucle no se expandirán dentro de ellas.

Como en #define, el ## sirve para juntar piezas de forma que si por ejemplo T es float, Vec_##T produce Vec_float.

Dentro del bucle, cualquier operador que siga a un # (p.ej. #,) se omite en la última vuelta.

typedef enum { #foreach { V {ESPACIO, NÚMERO, \ PALABRA_CLAVE, IDENTIFICADOR, OPERADOR} T_##V#, #foreach } } TipoDePieza; typedef enum { T_ESPACIO, T_NÚMERO, T_PALABRA_CLAVE, T_IDENTIFICADOR, T_OPERADOR } TipoDePieza;

Si una variable lleva el prefijo #, el resultado es una cadena con su contenido: si T es float, char* name = #T; produce char* name = "float";.

Los bucles se pueden anidar, y la lista de valores puede salir de una variable definida en un bucle exterior.

#foreach { VALORES {{ESPACIO, NÚMERO, \ PALABRA_CLAVE, IDENTIFICADOR, OPERADOR}} typedef enum { #foreach { V VALORES T_##V#, #foreach } } TipoDePieza; const char* const TipoDePieza_CADENA[] = { #foreach { V VALORES #V#, #foreach } }; #foreach } typedef enum { T_ESPACIO, T_NÚMERO, T_PALABRA_CLAVE, T_IDENTIFICADOR, T_OPERADOR } TipoDePieza; const char* const TipoDePieza_CADENA[] = { "ESPACIO", "NÚMERO", "PALABRA_CLAVE", "IDENTIFICADOR", "OPERADOR" };

Es posible iterar múltiples variables en paralelo usando tuplas de variables y valores, que deben tener el mismo número de elementos:

#foreach { {TIPO, PREFIJO, VALORES} { \ { TipoDePieza, T_, {ESPACIO, NÚMERO, \ PALABRA_CLAVE, IDENTIFICADOR, OPERADOR} }, \ { ConfDePúa, M_, {ENTRADA, SALIDA} } \ } typedef enum { #foreach { V VALORES PREFIJO##V#, #foreach } } TIPO; const char* const TIPO##_CADENA[] = { #foreach { V VALORES #V#, #foreach } }; #foreach } typedef enum { T_ESPACIO, T_NÚMERO, T_PALABRA_CLAVE, T_IDENTIFICADOR, T_OPERADOR } TipoDePieza; const char* const TipoDePieza_CADENA[] = { "ESPACIO", "NÚMERO", "PALABRA_CLAVE", "IDENTIFICADOR", "OPERADOR" }; typedef enum { M_ENTRADA, M_SALIDA } ConfDePúa; const char* const ConfDePúa_CADENA[] = { "ENTRADA", "SALIDA" };

Este ejemplo itera sobre una lista de campos para construir una struct y la correspondiente función imprime_...().

#include <stdio.h> #include <stdlib.h> #include <stdint.h> #pragma Cedro 1.0 void imprime_double(double n, FILE* salida) { fprintf(salida, "%f", n); } typedef uint32_t Color_ARGB; void imprime_Color_ARGB(Color_ARGB c, FILE* salida) { if ((c & 0xFF000000) == 0xFF000000) { fprintf(salida, "#%.6X", c & 0x00FFFFFF); } else { fprintf(salida, "#%.8X", c); } } #foreach { CAMPOS {{ \ { double, x, /** Posición X. */ }, \ { double, y, /** Posición Y. */ }, \ { Color_ARGB, color, /** Color 32 bits. */ } \ }} typedef struct Punto { #foreach { {TIPO, NOMBRE, COMENTARIO} CAMPOS TIPO NOMBRE; COMENTARIO #foreach } } Punto; void imprime_Punto(Punto punto, FILE* salida) { #foreach { {TIPO, NOMBRE, COMENTARIO} CAMPOS imprime_##TIPO(punto.NOMBRE, salida); putc('\n', salida); #foreach } } #foreach } int main(void) { Punto punto = { .x = 12.3, .y = 4.56, .color = 0xFFa3f193 }; imprime_Punto(punto, stderr); }
#include <stdio.h> #include <stdlib.h> #include <stdint.h> void imprime_double(double n, FILE* salida) { fprintf(salida, "%f", n); } typedef uint32_t Color_ARGB; void imprime_Color_ARGB(Color_ARGB c, FILE* salida) { if ((c & 0xFF000000) == 0xFF000000) { fprintf(salida, "#%.6X", c & 0x00FFFFFF); } else { fprintf(salida, "#%.8X", c); } } typedef struct Punto { double x; /** Posición X. */ double y; /** Posición Y. */ Color_ARGB color; /** Color 32 bits. */ } Punto; void imprime_Punto(Punto punto, FILE* salida) { imprime_double(punto.x, salida); putc('\n', salida); imprime_double(punto.y, salida); putc('\n', salida); imprime_Color_ARGB(punto.color, salida); putc('\n', salida); } int main(void) { Punto punto = { .x = 12.3, .y = 4.56, .color = 0xFFa3f193 }; imprime_Punto(punto, stderr); }

Este ejemplo completo define variantes para los tipos float, str, y cstr de una ristra/vector de capacidad variable con una función llamada añade_Vec_##T() para cada uno. Luego usa el _Generic del C11 para definir una macro/función añade() pseudo-polimórfica.

#include <stdint.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> // Para memcpy(). typedef struct str { uint8_t* start; uint8_t* end; } str; typedef char* cstr; // Nombres de tipo deben ser palabras. #pragma Cedro 1.0 #foreach { LISTA_DE_TIPOS {{float, str, cstr}} #foreach { T LISTA_DE_TIPOS /** Tipo Vector (ristra extensible). */ typedef struct { T* _; size_t len; size_t capacity; } Vec_##T; /** Añade una porción a un vector de elementos de este tipo. */ bool añade_Vec_##T(Vec_##T *v, const T *start, const T *end) { const size_t to_add = (size_t)(end - start); if (v->len + to_add > v->capacity) { const size_t new_capacity = v->len + (to_add < v->len? v->len: to_add); T * const new_start = realloc(v->_, new_capacity * sizeof(T)); if (!new_start) return false; v->_ = new_start; v->capacity = new_capacity; } memcpy(v->_ + v->len, start, to_add * sizeof(T)); v->len += to_add; return true; } #foreach } #foreach { DEFINE {#define} // Evita juntar las líneas. DEFINE añade(VEC, START, END) _Generic((VEC), \ #foreach { T LISTA_DE_TIPOS Vec_##T*: añade_Vec_##T#, \ #foreach } )(VEC, START, END) #foreach } #foreach } #include <stdio.h> int main(void) { Vec_cstr palabras = {0}; cstr animales[] = { "caballo", "gato", "pollo", "perro" }; cstr plantas [] = { "rábano", "trigo", "tomate" }; añade(&palabras, &animales[0], &animales[2]); añade(&palabras, &plantas[0], &plantas[3]); añade(&palabras, &animales[2], &animales[4]); for (cstr *p = palabras._, *fin = palabras._ + palabras.len; p != fin; ++p) { fprintf(stderr, "Palabra: \"%s\"\n", *p); } return 0; } #include <stdint.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> // Para memcpy(). typedef struct str { uint8_t* start; uint8_t* end; } str; typedef char* cstr; // Nombres de tipo deben ser palabras. /** Tipo Vector (ristra extensible). */ typedef struct { float* _; size_t len; size_t capacity; } Vec_float; /** Añade una porción a un vector de elementos de este tipo. */ bool añade_Vec_float(Vec_float *v, const float *start, const float *end) { const size_t to_add = (size_t)(end - start); if (v->len + to_add > v->capacity) { const size_t new_capacity = v->len + (to_add < v->len? v->len: to_add); float * const new_start = realloc(v->_, new_capacity * sizeof(float)); if (!new_start) return false; v->_ = new_start; v->capacity = new_capacity; } memcpy(v->_ + v->len, start, to_add * sizeof(float)); v->len += to_add; return true; } /** Tipo Vector (ristra extensible). */ typedef struct { str* _; size_t len; size_t capacity; } Vec_str; /** Añade una porción a un vector de elementos de este tipo. */ bool añade_Vec_str(Vec_str *v, const str *start, const str *end) { const size_t to_add = (size_t)(end - start); if (v->len + to_add > v->capacity) { const size_t new_capacity = v->len + (to_add < v->len? v->len: to_add); str * const new_start = realloc(v->_, new_capacity * sizeof(str)); if (!new_start) return false; v->_ = new_start; v->capacity = new_capacity; } memcpy(v->_ + v->len, start, to_add * sizeof(str)); v->len += to_add; return true; } /** Tipo Vector (ristra extensible). */ typedef struct { cstr* _; size_t len; size_t capacity; } Vec_cstr; /** Añade una porción a un vector de elementos de este tipo. */ bool añade_Vec_cstr(Vec_cstr *v, const cstr *start, const cstr *end) { const size_t to_add = (size_t)(end - start); if (v->len + to_add > v->capacity) { const size_t new_capacity = v->len + (to_add < v->len? v->len: to_add); cstr * const new_start = realloc(v->_, new_capacity * sizeof(cstr)); if (!new_start) return false; v->_ = new_start; v->capacity = new_capacity; } memcpy(v->_ + v->len, start, to_add * sizeof(cstr)); v->len += to_add; return true; } #define añade(VEC, START, END) _Generic((VEC), \ Vec_float*: añade_Vec_float, \ Vec_str*: añade_Vec_str, \ Vec_cstr*: añade_Vec_cstr \ )(VEC, START, END) #include <stdio.h> int main(void) { Vec_cstr palabras = {0}; cstr animales[] = { "caballo", "gato", "pollo", "perro" }; cstr plantas [] = { "rábano", "trigo", "tomate" }; añade(&palabras, &animales[0], &animales[2]); añade(&palabras, &plantas[0], &plantas[3]); añade(&palabras, &animales[2], &animales[4]); for (cstr *p = palabras._, *fin = palabras._ + palabras.len; p != fin; ++p) { fprintf(stderr, "Palabra: \"%s\"\n", *p); } return 0; }

Esto puede hacerse sin Cedro con la llamadas «X Macros».

The X macro technique was used extensively in the operating system and utilities for the DECsystem-10 as early as 1968, and probably dates back further to PDP-1 and TX-0 programmers at MIT.

Randy Meyers en «The New C: X Macros», Dr.Dobb’s 2001-05-01
CedroX macro
typedef enum { #foreach { V { \ ESPACIO, NÚMERO, \ PALABRA_CLAVE, IDENTIFICADOR, OPERADOR \ } T_##V#, #foreach } } TipoDePieza; #define LISTA_DE_VARIABLES \ X(ESPACIO), X(NÚMERO), \ X(PALABRA_CLAVE), X(IDENTIFICADOR), X(OPERADOR) typedef enum { #define X(V) T_##V LISTA_DE_VARIABLES #undef X } TipoDePieza; #undef LISTA_DE_VARIABLES
#foreach { VALORES {{ \ ESPACIO, NÚMERO, \ PALABRA_CLAVE, IDENTIFICADOR, OPERADOR \ }} typedef enum { #foreach { V VALORES T_##V#, #foreach } } TipoDePieza; const char* const TipoDePieza_CADENA[] = { #foreach { V VALORES #V#, #foreach } }; #foreach } #define LIST_OF_VARIABLES \ X(ESPACIO), X(NÚMERO), \ X(PALABRA_CLAVE), X(IDENTIFICADOR), X(OPERADOR) typedef enum { #define X(V) T_##V LISTA_DE_VARIABLES #undef X } TipoDePieza; const char* const TipoDePieza_CADENA[] = { #define X(V) #V LISTA_DE_VARIABLES #undef X }; #undef LISTA_DE_VARIABLES

Inclusión binaria: #binary-include

Inserta un fichero en forma de ristra de octetos, o de literal de cadena como se describe más adelante.

El nombre de fichero es relativo al fichero C incluyente.

#include <stdint.h> #pragma Cedro 1.0 const uint8_t imagen[] = { #embed "../images/cedro-32x32.png" };
#include <stdint.h> const uint8_t imagen[] = { /* cedro-32x32.png */ 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,… 0x00,0x00,0x00,0x20,0x00,0x00,0x00,… ⋮ };

La forma #include {...} era la original en Cedro y se mantiene de momento por compatibilidad pasada, mientras que #embed "..." sigue la norma futura C23 («N3017 #embed - a scannable, tooling-friendly binary resource inclusion mechanism») excepto que sólo admite caminos delimitados por comillas (""), no delimitados por cabrios (<>).

Nota: al usar #include { no debe haber ni más ni menos espacio entre #include y la llave {.

#include <stdint.h> #pragma Cedro 1.0 const uint8_t imagen #include {images/cedro-32x32.png} ;
Esta forma está obsoleta, y se va a eliminar.

#include <stdint.h> const uint8_t imagen [1559] = { /* cedro-32x32.png */ 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,… 0x00,0x00,0x00,0x20,0x00,0x00,0x00,… ⋮ };

#embed es más flexible porque permite añadir octetos antes o después del fichero insertado, y también combinar varios ficheros.

#pragma Cedro 1.0 const char const sombreador_de_fragmento[] = { #embed "shader.frag.glsl" , 0x00 // Cadena terminada con zero. };
const char const sombreador_de_fragmento[] = { /* shader.frag.glsl */ 0x23,0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,… 0x65,0x63,0x69,0x73,0x69,0x6F,0x6E,0x20,… ⋮ , 0x00 // Cadena terminada con zero. };
#pragma Cedro 1.0 const char const dos_líneas[] = { #embed "línea-de-texto-1.txt" , '\n', #embed "línea-de-texto-2.txt" , 0x00 };
const char const dos_líneas[] = { 0x46,0x69,0x72,0x73,0x74,0x20,0x6C,0x69,0x6E,0x65,0x2E , '\n', 0x53,0x65,0x63,0x6F,0x6E,0x64,0x20,0x6C,0x69,0x6E,0x65,0x2E , 0x00 };

En vez de insertar literales de octeto uno por uno, se pueden poner todos de una vez en un literal de cadena con la opción --embed-as-string=<límite>, aunque esta variante tiene ciertas limitaciones dependiendo del compilador C que se vaya a usar con el resultado.

Por ejemplo, el compilador C de Microsoft tiene un límite de 2048 octetos por cada literal de cadena, con un máximo de 65535 tras concatenar las cadenas que aparezcan juntas. (Véase «Maximum String Length»)

La norma ANSI/ISO C requiere que todos los compiladores acepten al menos 509 octetos en total tras la concatenación en C89, y 4096 en C99/C11/C17, pero otros compiladores como GCC y clang permiten cadenas muchísimo mayores.

Por eso es necesario especificar un límite para el tamaño: si el fichero sobrepasa ese límite, el resultado será una ristra de octetos en vez de una cadena.

En una prueba informal con /usr/bin/time -v, tomando los valores de la ejecución más rápida con un fichero de 8 MB, en una CPU IBM Power9 con los ficheros en disco RAM, la generación de código llevó un 26% menos tiempo que al usar octetos hexadecimales, y la compilación con GCC fue 28 veces más rápida usando 10 veces menos memoria. Con clang la compilación fue 72 veces más rápida que con octetos, usando 7 veces menos memoria.

Generar códigoCompilar con GCC 11Compilar con clang 12
cedro0.23 s1.56 MB27.66 s1328.13 MB30.44 s984.59 MB
cedro --embed-as-string=…0.17 s1.87 MB0.99 s125.32 MB0.42 s144.95 MB
bin2c0.03 s1.37 MB0.90 s108.70 MB0.52 s145.73 MB

En comparación con bin2c, la generación de código llevó cinco veces más tiempo, y la compilación es muy similar (±100ms, el resultado de bin2c compila más rápido en GCC, el de Cedro en clang) porque el formato es casi el mismo.

#pragma Cedro 1.0 const char const two lines[] = { #embed "text-line-1.txt" , '\n', #embed "text-line-2.txt" , 0x00 }; --embed-as-string=30 const char const dos_líneas[25] = /* línea-de-texto-1.txt */ "First line.""\n" /* línea-de-texto-2.txt */ "Second line.";

Nótese cómo Cedro se da cuenta de que el último octeto es cero y lo elimina, porque al haber una cadena justo antes, el compilador añadirá el terminador cero automáticamente.

En el siguiente ejemplo, al no haber un octeto cero tras la línea #embed, el tamaño 1559 corresponde exactamente al tamaño del fichero. Así evitamos tener un cero de más al final por usar cadenas.

#pragma Cedro 1.0 const char const sombreador_de_fragmento[] = { #embed "shader.frag.glsl" , 0x00 // Cadena terminada con zero. }; --embed-as-string=170 const char const sombreador_de_fragmento[164] = /* shader.frag.glsl */ "#version 140\n" "\n" "precision highp float; // needed only for version 1.30\n" "\n" "in vec3 ex_Color;\n" "out vec4 out_Color;\n" "\n" "void main(void)\n" "{\n" "\tout_Color = vec4(ex_Color,1.0);\n" "}\n";
#include <stdint.h> #pragma Cedro 1.0 const uint8_t imagen[] = { #embed "../images/cedro-32x32.png" }; --embed-as-string=1600 #include <stdint.h> const uint8_t imagen[1559] = /* cedro-32x32.png */ "\211PNG\r\n" "\032\n" "\000\000\000\rIHDR\000\000\000 \000\000\000 \b\002…" ⋮ …";
#include <stdint.h> #pragma Cedro 1.0 const uint8_t imagen #include {images/cedro-32x32.png} ;
Esta forma está obsoleta, y se va a eliminar.
--embed-as-string=1600 #include <stdint.h> const uint8_t imagen [1559] = /* cedro-32x32.png */ "\211PNG\r\n" "\032\n" "\000\000\000\rIHDR\000\000\000 \000\000\000 \b\002…" ⋮ …";

Insertar directamente el código en el programa es muy conveniente pero va a enlentecer la compilación. La manera de reducir el problema, aparte de usar --embed-as-string=<límite>, es compilar esta parte por separado como se puede ver en template/Makefile.nanovg.mk o en este ejemplo: (otra manera sería usar cabeceras precompiladas)

assets.c#include <stdint.h> #include <stdlib.h> #pragma Cedro 1.0 const uint8_t imagen[] = { #embed "../images/cedro-32x32.png" }; const size_t sizeof_imagen = sizeof(imagen);
main.c#include <stdint.h> #include <stdlib.h> #include <stdio.h> #include <limits.h> extern const uint8_t imagen[]; extern const size_t sizeof_imagen; int main(void) { unsigned int suma = 0; for (size_t i = 0; i < sizeof_imagen; ++i) { suma += imagen[i]; } fprintf(stderr, "La suma (módulo UINT_MAX=%u) de los bytes de la imagen es %u.\n", UINT_MAX, suma); }
cedrocc -c -o assets.o assets.c -std=c99 cedrocc -c -o main.o main.c -std=c99 cc -o programa main.o assets.o

Esta característica es una vieja idea y hay varias implementaciones anteriores, por ejemplo xxd (como xxd -i, manual en inglés) que usé hace muchos años y la tiene desde 1994.

Más recientemente, la macro include_bytes!() me ha sido muy útil en mis programas en Rust.

La idea de producir literales de cadena en vez de ristras de octetos me la dió este comentario:

the way that you’ve lowered them is absolutely the worst case for compiler performance. The compiler needs to create a literal constant object for every byte and an array object wrapping them. If you lower them as C string literals with escapes, you will generate code that compiles much faster. For example, the cedro-32x32.png example lowered as “\x89\x50\x4E\x47\0D\x0A\x1A…” will be faster and use less memory in the C compiler.

David Chisnall en Lobsters, 2021-08-12

I did not realize that, you are right of course! I know there are limits to the size of string literals, but maybe that does not apply if you split them. I’ll have to check that out.

EDIT: I’ve just found out that bin2c (which I knew existed but haven’t used) does work in the way you describe, with strings instead of byte arrays: https://github.com/adobe/bin2c#comparison-to-other-tools It does mention the string literal size limit. I suspect you know, but for others reading this: the C standard defines some sizes that all compilers must support as a minimum, and one of them is the string literal maximum size. Compilers are free to allow bigger tokens when parsing.

I’m concerned that it would be a problem, because as I hinted above my use case includes compiling on old platforms with outdated C compilers (sometimes for fun, others because my customers need that) so it is important that cedro does not fail any more than strictly necessary when running on unusual machines.

Thinking about it, I could use strings when under the length limit, but those would be the cases where the performance difference would be small. I’ll keep things like this for now, but thanks to you I’ll take these aspects into account. EDIT END.

Alberto González Palomo en Lobsters, 2021-08-12

Un ejemplo de ese método es el bin2c de Adobe publicado en 2020 (no es el mismo que el bin2c of 2012 que produce literales de octeto como xxd), y aunque no he mirado su código fuente, Cedro sigue lo especificado en su documentación excepto los finales de línea, donde Cedro parte el literal de cadena de la misma forma que suele hacerse a mano y además limita el tamaño de cada cadena individual a 500 octetos con la esperanza de que funcione en compiladores antiguos.

Más referencias (en inglés) con información sobre otros métodos:

Literales numéricos: #number-literals

Permite usar separadores de dígitos (' o _) y literales binarios (0b…).

A partir de C23, el separador apóstrofe y los literales binarios son estándar. Si tu compilador acepta C23, puedes usar la opción --c23 para dejarlos como están.

Yo prefiero el subrayado, pero el comité del C23 no pudo usarlo por compatibilidad con el C++, que no pudo usarlo porque choca con los sufijos definibles que ya usaban el subrayado:

The syntax of digit separators is ripe for bikeshed discussions about what character to use as the separator token. The most common suggestions from the community are to use underscore (_), a single quote ('), or whitespace as these seem like they would not produce any lexical ambiguities. However, two of these suggestions turn out to have usability issues in practice.

[...] Use of an underscore character is also problematic, but only for C++. C++ has the ability for users to define custom literal suffixes [WG21 N2765], and these suffixes are required to be named with a legal C++ identifier, which includes the underscore character. Using an underscore as a digit separator then becomes ambiguous for a construct like 0x1234_a (does the _a require a lookup for a user-defined literal suffix or is it part of the hexadecimal constant?).

Aaaron Ballman en N2606 “Digit Separators”.

Varios compiladores, por ejemplo GCC a partir de v4.3, permiten literales binarios como extensión del lenguage C.

123'45'67 1234567
123_45_67 1234567
123'45_67 1234567
0b10100110 0xA6
0b_1010_0110 0xA6
0b'1010_0110 0xA6
↑ Sumario