Cómo hacer una macro variadic (número variable de argumentos)


196

Quiero escribir una macro en C que acepte cualquier número de parámetros, no un número específico

ejemplo:

#define macro( X )  something_complicated( whatever( X ) )

donde Xhay cualquier número de parámetros

Necesito esto porque whateverestá sobrecargado y se puede llamar con 2 o 4 parámetros.

Traté de definir la macro dos veces, ¡pero la segunda definición sobrescribió la primera!

El compilador con el que estoy trabajando es g ++ (más específicamente, mingw)


8
¿Quieres C o C ++? Si está utilizando C, ¿por qué está compilando con un compilador de C ++? Para usar las macros variadic C99 adecuadas, debe compilar con un compilador C que admita C99 (como gcc), no un compilador C ++, ya que C ++ no tiene macros variadic estándar.
— Chris Lutz el

Bueno, asumí C ++ es un súper conjunto de C en este sentido ..
— Hasen

tigcc.ticalc.org/doc/cpp.html#SEC13 tiene una explicación detallada de las macros variables.
— Gnubie

Una buena explicación y ejemplo está aquí http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
— zafarulq

3
Para futuros lectores: C no es una subestimación de C ++. Comparten muchas cosas, pero hay reglas que evitan que sean subconjuntos y superconjuntos entre sí.
— Pharap

Respuestas:


295

Forma C99, también compatible con el compilador VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)

8
No creo que C99 requiera el ## antes de VA_ARGS . Eso podría ser solo VC ++.
— Chris Lutz el

97
La razón de ## antes de VA_ARGS es que se traga la coma anterior en caso de que la lista de argumentos variables esté vacía, por ejemplo. FOO ("a") se expande a printf ("a"). Esta es una extensión de gcc (y vc ++, tal vez), C99 requiere al menos un argumento para estar presente en lugar de los puntos suspensivos.
— jpalecek

108
##No es necesario y no es portátil. #define FOO(...) printf(__VA_ARGS__)hace el trabajo de forma portátil; el fmtparámetro puede omitirse de la definición.
— alecov

44
IIRC, el ## es específico de GCC y permite el paso de cero parámetros
— Mawg dice que reinstale a Monica el

10
La sintaxis ## - también funciona con llvm / clang y el compilador de Visual Studio. Por lo tanto, puede que no sea portátil, pero es compatible con los principales compiladores.
— K. Biermann

37

__VA_ARGS__Es la forma estándar de hacerlo. No use hacks específicos del compilador si no es necesario.

Estoy realmente molesto porque no puedo comentar sobre la publicación original. En cualquier caso, C ++ no es un superconjunto de C. Es realmente tonto compilar su código C con un compilador de C ++. No hagas lo que Donny Don't hace.


8
"Es realmente una tontería compilar su código C con un compilador C ++" => No todos lo consideran (incluido yo). Consulte, por ejemplo, las pautas principales de C ++: CPL.1 : prefiera C ++ a C , CPL.2: si debe usar C, use el subconjunto común de C y C ++ y compile el código C como C ++ . Me cuesta pensar qué "C-solo-ismos" uno realmente necesita para que valga la pena no programar en el subconjunto compatible, y los comités C y C ++ han trabajado arduamente para hacer que ese subconjunto compatible esté disponible.
— HostileFork dice que no confíes en SE

44
@HostileFork Lo suficientemente justo, aunque, por supuesto, a la gente de C ++ le gustaría alentar el uso de C ++. Sin embargo, otros no están de acuerdo; Múltiples servidores Linux Torvalds, por ejemplo, al parecer, ha rechazado propuso parches del kernel de Linux que intentan reemplazar el identificador classcon klasspara permitir compilar con un compilador de C ++. También tenga en cuenta que hay algunas diferencias que lo harán tropezar; por ejemplo, el operador ternario no se evalúa de la misma manera en ambos idiomas, y la inlinepalabra clave significa algo completamente diferente (como aprendí de una pregunta diferente).
— Kyle Strand

3
Para proyectos de sistemas realmente multiplataforma como un sistema operativo, realmente desea adherirse a C estricto, porque los compiladores de C son mucho más comunes. En los sistemas integrados, todavía hay plataformas sin compiladores de C ++. (¡Hay plataformas con solo compiladores C transitables!) Los compiladores C ++ me ponen nervioso, especialmente para los sistemas ciberfísicos, y supongo que no soy el único programador / C integrado con esa sensación.
— Bajista

2
@downbeat Ya sea que use C ++ para producción o no, si le preocupa el rigor, poder compilar con C ++ le otorga poderes mágicos para el análisis estático. Si tiene una consulta que desea hacer de una base de código C ... preguntándose si ciertos tipos se usan de ciertas maneras, aprender a usar type_traits puede construir herramientas específicas para ello. Lo que pagaría mucho dinero por una herramienta de análisis estático de C se puede hacer con un poco de conocimiento de C ++ y el compilador que ya tiene ...
— HostileFork dice que no confíe en SE

1
Estoy hablando de la cuestión de Linux. (Me acabo de dar cuenta de que dice "Linux Torvalds", ja!)
— Downbeat

28

No creo que sea posible, podrías fingirlo con dobles padres ... siempre y cuando no necesites los argumentos individualmente.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))

21
Si bien es posible tener una macro variable, usar un paréntesis doble es un buen consejo.
— David Rodríguez - dribeas

2
El compilador XC de Microchip no admite macros variables, por lo que este truco de paréntesis doble es lo mejor que puede hacer.
— gbmhunter

10
#define DEBUG

#ifdef DEBUG
  #define PRINT print
#else
  #define PRINT(...) ((void)0) //strip out PRINT instructions from code
#endif 

void print(const char *fmt, ...) {

    va_list args;
    va_start(args, fmt);
    vsprintf(str, fmt, args);
        va_end(args);

        printf("%s\n", str);

}

int main() {
   PRINT("[%s %d, %d] Hello World", "March", 26, 2009);
   return 0;
}

Si el compilador no comprende macros variables, también puede eliminar PRINT con cualquiera de los siguientes:

#define PRINT //

o

#define PRINT if(0)print

El primero comenta las instrucciones de IMPRESIÓN, el segundo evita la instrucción de IMPRESIÓN debido a una condición NULL si. Si se establece la optimización, el compilador debe eliminar instrucciones nunca ejecutadas como: if (0) print ("hello world"); o ((nulo) 0);


8
#define PRINT // no reemplazará PRINT con //
— bitc

8
#definir PRINT if (0) print tampoco es una buena idea porque el código de llamada puede tener su propio else-if para llamar a PRINT. Mejor es: #define PRINT if (true); else print
— bitc

3
El estándar "no hacer nada, con gracia" es hacer {} while (0)
— vonbrand

La ifversión adecuada de "no haga esto" que tenga en cuenta la estructura del código es: if (0) { your_code } elseun punto y coma después de que su expansión de macro finalice el else. La whileversión se ve así: while(0) { your_code } el problema con la do..whileversión es que el código do { your_code } while (0)se realiza una vez, garantizado. En los tres casos, si your_codeestá vacío, es correcto do nothing gracefully.
— Jesse Chisholm

4

explicado para g ++ aquí, aunque es parte de C99, por lo que debería funcionar para todos

http://www.delorie.com/gnu/docs/gcc/gcc_44.html

ejemplo rápido:

#define debug(format, args...) fprintf (stderr, format, args)

3
Las macros variadic de GCC no son macros variadic C99. GCC tiene macros variables C99, pero G ++ no las admite, porque C99 no es parte de C ++.
— Chris Lutz el

1
En realidad, g ++ compilará macros C99 en archivos C ++. Sin embargo, emitirá una advertencia si se compila con '-pedantic'.
— Alex B

2
No es C99. C99 usa la macro VA_ARGS ).
— qrdl

1
C ++ 11 también es compatible __VA_ARGS__, aunque también es compatible con compiladores en versiones anteriores, como una extensión.
— Ethouris

1
Esto no funciona para printf ("hola"); donde no hay var args. ¿Alguna forma genérica de arreglar esto?
— BTR Naidu
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.