DMarlon Studio

Lección 16 · 16 de 20

Funciones variádicas · C

Escriba en C funciones con un número variable de argumentos usando stdarg.h: una media de n valores y un registro de mensajes al estilo de printf.

C · Funciones variádicas en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función variádica acepta un número variable de argumentos, como printf. Se declara con ... después de al menos un parámetro con nombre: double average(int count, ...). Las herramientas están en `stdarg.h`.

Dentro se declara un va_list y se inicia con `va_start(args, count)`, que recibe el último parámetro con nombre. Cada `va_arg(args, double)` devuelve el siguiente argumento con el tipo que se le indica, y `va_end` cierra la lista. Cada va_start tiene que llevar su va_end.

La función no tiene forma de saber cuántos argumentos le han llegado ni de qué tipo son: se lo tiene que decir quien llama. average lo sabe por count; printf, por los % del formato. Si no coinciden, como average(3, 7.5), se leería basura sin ningún aviso. Además, en los ... los float llegan convertidos a double y los char y short, a int; por eso va_arg pide double.

log_msg es un registro de mensajes al estilo de printf: escribe el nivel entre corchetes y pasa el formato y los demás argumentos a `vprintf`, la versión de printf que recibe un va_list. Existen también vfprintf, vsnprintf y compañía, pensadas precisamente para esto.

%% en el formato escribe un signo %, como en 91%.

Código · main.c

#include <stdarg.h>#include <stdio.h>// The first argument says how many numbers follow.double average(int count, ...) {    va_list args;    va_start(args, count);         // start after the last named parameter    double sum = 0;    for (int i = 0; i < count; i++)        sum += va_arg(args, double);  // floats arrive as double    va_end(args);                  // always pair va_start with va_end    return count ? sum / count : 0;}// A printf-like logger: the format string says what the extra arguments are.void log_msg(const char *level, const char *format, ...) {    va_list args;    va_start(args, format);    printf("[%-5s] ", level);    vprintf(format, args);         // the v... versions take a va_list    printf("\n");    va_end(args);}int main(void) {    printf("Average of 3: %.2f\n", average(3, 7.5, 9.0, 6.0));    printf("Average of 5: %.2f\n", average(5, 4.0, 8.0, 15.0, 16.0, 23.0));    printf("Average of 0: %.2f\n", average(0));    log_msg("INFO", "Server started on port %d", 8080);    log_msg("WARN", "Disk %s is %d%% full", "/data", 91);    log_msg("ERROR", "Order %s: %d items, total %.2f", "A-104", 3, 59.85);    return 0;}

Salida · cc main.c -o main && ./main

Average of 3: 7.50
Average of 5: 13.20
Average of 0: 0.00
[INFO ] Server started on port 8080
[WARN ] Disk /data is 91% full
[ERROR] Order A-104: 3 items, total 59.85

Para recordar

  • ... después de un parámetro con nombre.
  • va_start, va_arg y va_end, siempre en pareja inicio y fin.
  • Quien llama indica cuántos y de qué tipo son los argumentos.
  • Los float llegan como double; los char, como int.

Ejercicio

Escriba int max_of(int count, ...), que devuelva el mayor de count enteros, y una variante de log_msg que escriba en un búfer con vsnprintf en vez de en la pantalla.