Como criar uma macro variável (número variável de argumentos)

196

Eu quero escrever uma macro em C que aceite qualquer número de parâmetros, não um número específico

exemplo:

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

Onde Xestá qualquer número de parâmetros

Eu preciso disso porque whateverestá sobrecarregado e pode ser chamado com 2 ou 4 parâmetros.

Tentei definir a macro duas vezes, mas a segunda definição substituiu a primeira!

O compilador com o qual estou trabalhando é g ++ (mais especificamente, mingw)

hasen
fonte
8
Você quer C ou C ++? Se você estiver usando C, por que está compilando com um compilador C ++? Para usar macros variadas C99 adequadas, você deve compilar com um compilador C que suporte C99 (como o gcc), não um compilador C ++, pois o C ++ não possui macros variadas padrão.
31720 Chris Lutz
Bem, eu assumi C ++ é um conjunto de super C a este respeito ..
Hasen
tigcc.ticalc.org/doc/cpp.html#SEC13 tem uma explicação detalhada de macros variadas.
Gnubie 25/10
Uma boa explicação e exemplo é aqui http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq
3
Para futuros leitores: C não é um subeste de C ++. Eles compartilham muitas coisas, mas existem regras que os impedem de serem subconjuntos e superconjuntos.
Pharap

Respostas:

295

Maneira C99, também suportada pelo compilador VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)
Alex B
fonte
8
Eu não acho que o C99 exija o ## antes do VA_ARGS . Isso pode ser apenas VC ++.
31720 Chris Lutz
97
A razão para ## antes do VA_ARGS é que ela engole a vírgula anterior, caso a lista de argumentos variáveis ​​esteja vazia, por exemplo. FOO ("a") se expande para printf ("a"). Esta é uma extensão do gcc (e vc ++, talvez), o C99 exige que pelo menos um argumento esteja presente no lugar das reticências.
jpalecek
108
##não é necessário e não é portátil. #define FOO(...) printf(__VA_ARGS__)faz o trabalho da maneira portátil; o fmtparâmetro pode ser omitido da definição.
Alecov
4
IIRC, o ## é específico do GCC e permite a passagem de zero parâmetros
Mawg diz que restabelece Monica em 23/07/12
10
A sintaxe ## - também funciona com llvm / clang e o compilador do Visual Studio. Portanto, pode não ser portátil, mas é suportado pelos principais compiladores.
K.Kiermann
37

__VA_ARGS__é a maneira padrão de fazer isso. Não use hacks específicos do compilador, se não for necessário.

Estou muito chateado por não poder comentar a postagem original. De qualquer forma, C ++ não é um superconjunto de C. É realmente uma bobagem compilar seu código C com um compilador C ++. Não faça o que Donny não faz.

cmccabe
fonte
8
"É realmente bobo compilar seu código C com um compilador C ++" => Não é considerado por todos (inclusive eu). Veja, por exemplo, as principais diretrizes do C ++: CPL.1: Prefira C ++ a C , CPL.2: se você precisar usar C, use o subconjunto comum de C e C ++ e compile o código C como C ++ . Estou com muita dificuldade para pensar em quais "ismos apenas C" realmente precisamos fazer valer a pena não programar no subconjunto compatível, e os comitês C e C ++ trabalharam arduamente para tornar esse subconjunto compatível disponível.
HostileFork diz que não confia em SE
4
@HostileFork Fair o suficiente, embora é claro que o pessoal do C ++ gostaria de incentivar o uso do C ++. Outros discordam; O Torvalds do Linux, por exemplo, aparentemente rejeitou vários patches de kernel Linux propostos que tentam substituir o identificador classpor klasspermitir compilar com um compilador C ++. Observe também que existem algumas diferenças que o farão tropeçar; por exemplo, o operador ternário não é avaliado da mesma maneira nos dois idiomas, e a inlinepalavra - chave significa algo completamente diferente (como aprendi com uma pergunta diferente).
Kyle Strand
3
Para projetos de sistemas realmente multiplataforma, como um sistema operacional, você realmente deseja aderir ao rigoroso C, porque os compiladores C são muito mais comuns. Em sistemas embarcados, ainda existem plataformas sem compiladores C ++. (Existem plataformas com apenas compiladores C transitáveis!) Os compiladores C ++ me deixam nervoso, principalmente em sistemas ciber-físicos, e eu acho que não sou o único programador de software / C incorporado com esse sentimento.
downbeat
2
@downbeat Se você usa C ++ para produção ou não, se você está preocupado com o rigor, a capacidade de compilar com C ++ fornece poderes mágicos para a análise estática. Se você tem uma consulta que deseja fazer de uma base de código C ... se perguntando se certos tipos são usados ​​de determinadas maneiras, aprender a usar type_traits pode criar ferramentas direcionadas para ela. O que você pagaria muito dinheiro por uma ferramenta de análise estática de C fazer pode ser feita com um pouco de conhecimento em C ++ e do compilador que você já possui ... #
HostileFork diz que não confia em SE
1
Estou falando da questão do Linux. (Eu notei que ele diz que "Linux Torvalds" ha!)
downbeat
28

Eu não acho que isso seja possível, você pode fingir com parênteses duplos ... desde que não precise dos argumentos individualmente.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))
eduffy
fonte
21
Embora seja possível ter uma macro variável, o uso de parênteses duplos é um bom conselho.
David Rodríguez - dribeas 25/03/2009
2
O compilador XC da Microchip não suporta macros variadas e, portanto, esse truque de parênteses duplos é o melhor que você pode fazer.
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;
}

Se o compilador não entender macros variadas, você também poderá remover o PRINT com um dos seguintes procedimentos:

#define PRINT //

ou

#define PRINT if(0)print

O primeiro comenta as instruções PRINT, o segundo impede a instrução PRINT devido a uma condição NULL if. Se a otimização estiver definida, o compilador deve remover instruções nunca executadas, como: if (0) print ("hello world"); ou ((nulo) 0);

jpalecek
fonte
8
#define PRINT // não substituirá PRINT por //
bitc
8
#define PRINT se (0) print também não é uma boa ideia, porque o código de chamada pode ter seu próprio outro-if para chamar PRINT. Melhor é: #define PRINT if (true); else print
bitc
3
O padrão "nada fazer, graciosamente" é fazer {} while (0)
vonbrand
A ifversão adequada do "não faça isso" que leva em consideração a estrutura do código é: if (0) { your_code } elseponto-e-vírgula após a expansão da macro else. A whileversão é semelhante a: while(0) { your_code } O problema com a do..whileversão é que o código do { your_code } while (0)é feito uma vez, garantido. Nos três casos, se your_codeestiver vazio, é apropriado do nothing gracefully.
Jesse Chisholm
4

explicado para g ++ aqui, embora faça parte do C99, portanto, deve funcionar para todos

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

exemplo rápido:

#define debug(format, args...) fprintf (stderr, format, args)
DarenW
fonte
3
As macros variadicas do GCC não são macros variadicas C99. O GCC possui macros variadas do C99, mas o G ++ não as suporta, porque o C99 não faz parte do C ++.
31730 Chris Lutz
1
Na verdade, o g ++ compila macros C99 em arquivos C ++. Ele emitirá um aviso, no entanto, se compilado com '-pedantic'.
Alex B
2
Não é C99. C99 usa a macro VA_ARGS ).
23409 qrdl
1
O C ++ 11 também suporta __VA_ARGS__, embora também sejam suportados por compiladores em versões anteriores, como uma extensão.
Ethouris
1
Isso não funciona para printf ("oi"); onde não há var argumentos. Alguma maneira genérica de corrigir isso?
BTR Naidu