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)


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__)

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.


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))

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);


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)

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.