¿Cómo envolver printf() en una función o macro?

6 minutos de lectura

avatar de usuario
Vorac

Esto puede sonar como una pregunta de entrevista, pero en realidad es un problema práctico.

Estoy trabajando con una plataforma integrada y solo tengo disponibles los equivalentes de esas funciones:

  • imprimirf()
  • snprintf()

Además, el printf() Es probable que la implementación (y la firma) cambien en un futuro cercano, por lo que las llamadas deben residir en un módulo separado para que sea fácil de migrar más adelante.

Dado eso, ¿puedo envolver las llamadas de registro en alguna función o macro? El objetivo es que mi código fuente llame THAT_MACRO("Number of bunnies: %d", numBunnies); en mil lugares, pero las llamadas a las funciones anteriores se ven solo en un solo lugar.

Compilador: arm-gcc -std=c99

Editar: solo por mencionar, pero las mejores prácticas posteriores a 2000 y probablemente mucho antes, las funciones en línea son mucho mejores que las macros por numerosas razones.

  • ¿Su compilador admite macros variadas?

    – alk

    17 dic 2013 a las 16:37


  • ¿Qué restricciones del compilador existen? Si esto debe ejecutarse en una versión de C anterior a C99, será difícil lograrlo de forma portátil como una macro.

    – ldav1s

    17 dic 2013 a las 16:37


  • @KerrekSB Pensé ¿POR QUÉ? ¿Se bloquearon los comentarios en estos días?

    – Roddy

    17 de diciembre de 2013 a las 16:41

Hay 2 formas de hacer esto:

  1. macro variadrica

    #define my_printf(...) printf(__VA_ARGS__)
    
  2. función que reenvía va_args

    #include <stdarg.h>
    #include <stdio.h>
    
    void my_printf(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
    }
    

también hay vsnprintf, vfprintf y lo que se te ocurra en stdio.

  • Además, puede encontrar documentación sobre macros (y macros variables) aquí.

    – jmlemetayer

    17 de diciembre de 2013 a las 16:41


  • @Roddy Sí, si desea reenviar todos los argumentos. Lo desaconsejaría ya que no puede definir una macro sin operaciones de esa manera. Con una macro similar a una función, siempre puede hacer que no funcione si es necesario.

    – sergey l.

    17 de diciembre de 2013 a las 16:42

  • Desearía que alguien editara esta respuesta para poder eliminar mi voto a favor. NO tengo vprintf u otras fantasías. Embebido, ya sabes.

    – Vorác

    18 de diciembre de 2013 a las 12:37

  • Lo siento por el tono. Realmente no puedo usar las bibliotecas estándar para esto. Es una plataforma personalizada basada en ARM.

    – Vorác

    18 de diciembre de 2013 a las 13:33

  • He estado probando varios métodos como este, pero 1. No puedo hacer macros que funcionen a través de espacios de nombres; #define Log::WriteLine(_Format, ...) printf(_Format, __VA_ARGS__) no funciona Y 2. No muestra el %s etc. en verde lima, o validar la entrada… y por lo tanto es inútil como sustituto. Si hay alguna forma de obtener un printf personalizado que muestre el %s etc. en verde lima, con la validación que es imprescindible para su uso?

    – apache

    18 de enero de 2020 a las 16:25

avatar de usuario
ldav1s

Como puedes usar C99, lo envolvería en un macro variádica:

#define TM_PRINTF(f_, ...) printf((f_), __VA_ARGS__)
#define TM_SNPRINTF(s_, sz_, f_, ...) snprintf((s_), (sz_), (f_), __VA_ARGS__)

como no dijiste que tienes vprintf o algo asi Si tiene algo así, podría envolverlo en una función como la que Sergey L ha proporcionado en su respuesta.


El TM_PRINTF anterior no funciona con una lista VA_ARGS vacía. Al menos en GCC es posible escribir:

#define TM_PRINTF(f_, ...) printf((f_), ##__VA_ARGS__)

Los dos signos ## eliminan el exceso de coma delante de ellos si __VA_ARGS__ esta vacio.

  • TM_PRINTF("Starting ei-oh!"); rendimientos error: expected expression before ')' token. Sin el argumento de cadena de formato, funciona. ¿Los argumentos variádicos deben ser distintos de cero en número?

    – Vorác

    18 de diciembre de 2013 a las 12:50

  • Parece que el error anterior no se puede corregir sin las extensiones gcc: gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

    – Vorác

    18 de diciembre de 2013 a las 13:07

  • Puede resolver el problema eliminando la parte _f y manteniendo solo la cosa varidac. Funciona sin problemas para mí, pero no sé si está fuera de estándar.

    – Lesto

    30 de septiembre de 2016 a las 10:12


  • @lesto, sí, “funciona”, pero eliminando f_ haría TM_PRINTF() admisible. Si esa cadena estuviera en el código, el compilador se quejaría más tarde sobre printf() que no coincide con su firma de función. Es mejor hacer que el compilador se queje TM_PRINTF en este caso, ya que eso es más visible.

    – ldav1s

    30 de septiembre de 2016 a las 14:39

  • @ ldav1s No entendí lo que querías decir con una macro “variádica”. Como por arte de magia esos puntos suspensivos ... reenviarlos a __VA_ARGS__ ?

    – John Strod

    29/10/2016 a las 17:04

Si puedes vivir con tener que envolver la llamada dos paréntesis, puedes hacerlo así:

#define THAT_MACRO(pargs)    printf pargs

Entonces úsalo:

THAT_MACRO(("This is a string: %s\n", "foo"));
           ^
           |
          OMG

Esto funciona ya que, desde el punto de vista del preprocesador, la lista completa de argumentos se convierte en un macroargumento, que se sustituye por el paréntesis.

Esto es mejor que simplemente hacer

#define THAT_MACRO printf

Ya que te permite definirlo:

#define THAT_MACRO(pargs)  /* nothing */

Esto “comerá” los argumentos de la macro, nunca serán parte del código compilado.

ACTUALIZAR Por supuesto, en C99 esta técnica es obsoleta, solo use una macro variádica y sea feliz.

  • Dios mío, gracias por la gran respuesta. Supongo que recibirás votos a favor de todos los pobres programadores de C89 durante los próximos años 🙂

    – Vorác

    18 de diciembre de 2013 a las 12:51

  • Un efecto secundario importante de esta técnica: todas las llamadas a THAT_MACRO necesitará paréntesis dobles, incluso con llamadas de un solo argumento, por ejemplo THAT_MACRO(("Foo Bar")). –Con amor, un pobre programador de C89 de varios años después.

    – drmuelr

    19 de julio de 2016 a las 16:02

#define TM_PRINTF(f_, ...) printf((f_), ##__VA_ARGS__)

los ## token habilitará el uso TM_PRINTF("aaa");

avatar de usuario
EKons

#define PRINTF(...) printf(__VA_ARGS__)

Esto funciona así:

Define la macro parametrizada PRINTF para aceptar (hasta) argumentos infinitos, luego la preprocesa desde PRINTF(...) para printf(__VA_ARGS__). __VA_ARGS__ se usa en definiciones de macros parametrizadas para denotar los argumentos dados (porque no puedes nombrar argumentos infinitos, ¿verdad?).

avatar de usuario
Comunidad

¿Biblioteca limitada? ¿Sistema Integrado? ¿Necesita el mayor rendimiento posible? ¡No hay problema!

Como se demuestra en esta respuesta a esta pregunta, puede usar el lenguaje ensamblador para envolver funciones que no aceptan VA_LIST en las que sí lo hacen, ¡implementando su propio vprintf a bajo costo!

Si bien esto funcionará, y es casi seguro que dará como resultado el rendimiento y la abstracción que desea, solo recomendaría que obtenga una biblioteca estándar con más funciones, tal vez cortando partes de uClibc. Tal solución seguramente será una respuesta más portátil y, en general, más útil que usar el ensamblaje, a menos que necesite absolutamente todos los ciclos.

Es por eso que tales proyectos existen, después de todo.

avatar de usuario
Behzad

Esta es una versión ligeramente modificada de la excelente respuesta de @ ldav1 que imprime el tiempo antes del registro:

#define TM_PRINTF(f_, ...)                                                                            \
{                                                                                                 \
    struct tm _tm123_;                                                                            \
    struct timeval _xxtv123_;                                                                     \
    gettimeofday(&_xxtv123_, NULL);                                                               \
    localtime_r(&_xxtv123_.tv_sec, &_tm123_);                                                     \
    printf("%2d:%2d:%2d.%d\t", _tm123_.tm_hour, _tm123_.tm_min, _tm123_.tm_sec, _xxtv123_.tv_usec); \
    printf((f_), ##__VA_ARGS__);                                                                  \
};

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad