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)

Hasen
fuente
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__)
Alex B
fuente
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.

cmccabe
fuente
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))
eduffy
fuente
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);

jpalecek
fuente
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)
DarenW
fuente
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