Eu estou tentando escrever um programa onde os nomes de algumas funções dependem do valor de uma determinada variável de macro com uma macro como esta:
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
int NAME(some_function)(int a);
Infelizmente, a macro NAME()
transforma isso em
int some_function_VARIABLE(int a);
ao invés de
int some_function_3(int a);
Portanto, esse é claramente o caminho errado. Felizmente, o número de diferentes valores possíveis para VARIABLE é pequeno, então eu posso simplesmente fazer #if VARIABLE == n
e listar todos os casos separadamente, mas fiquei imaginando se existe uma maneira inteligente de fazer isso.
#define A 0 \n #define M a ## A
: ter dois##
não é a chave.Respostas:
Pré-processador C padrão
Dois níveis de indireção
Em comentário a outra resposta, o Cade Roux perguntou por que isso precisa de dois níveis de indireção. A resposta irreverente é porque é assim que o padrão exige que ele funcione; você tende a achar que também precisa do truque equivalente com o operador de stringing.
A seção 6.10.3 da norma C99 abrange a 'substituição de macro' e a 6.10.3.1 abrange a 'substituição de argumento'.
Na invocação
NAME(mine)
, o argumento é 'meu'; é totalmente expandido para 'meu'; é então substituído na cadeia de substituição:Agora, a macro EVALUATOR é descoberta e os argumentos são isolados como 'mine' e 'VARIABLE'; o último é totalmente expandido para '3' e substituído na string de substituição:
A operação disso é coberta por outras regras (6.10.3.3 'O operador ##'):
Portanto, a lista de substituição contém
x
seguido por##
e também##
seguido pory
; então nós temos:e eliminar os
##
tokens e concatenar os tokens de ambos os lados combina 'mine' com '_' e '3' para produzir:Este é o resultado desejado.
Se olharmos para a pergunta original, o código foi (adaptado para usar 'mine' em vez de 'some_function'):
O argumento para NAME é claramente 'meu' e é totalmente expandido.
Seguindo as regras de 6.10.3.3, encontramos:
que, quando os
##
operadores são eliminados, mapeia para:exatamente como relatado na pergunta.
Pré-processador C tradicional
Robert Rüger pergunta :
Talvez, e talvez não - isso depende do pré-processador. Uma das vantagens do pré-processador padrão é que ele possui esse recurso que funciona de maneira confiável, ao passo que houve implementações diferentes para pré-processadores pré-padrão. Um requisito é que, quando o pré-processador substitui um comentário, ele não gera um espaço como o pré-processador ANSI deve fazer. O pré-processador C do GCC (6.3.0) C atende a esse requisito; o pré-processador Clang do XCode 8.2.1 não.
Quando funciona, isso faz o trabalho (
x-paste.c
):Observe que não há um espaço entre
fun,
eVARIABLE
- isso é importante porque, se presente, ele é copiado para a saída e você acaba commine_ 3
o nome, que não é sintaticamente válido, é claro. (Agora, por favor, posso recuperar meu cabelo?)Com o GCC 6.3.0 (em execução
cpp -traditional x-paste.c
), recebo:Com o Clang do XCode 8.2.1, recebo:
Esses espaços estragam tudo. Noto que ambos os pré-processadores estão corretos; diferentes pré-processadores pré-padrão exibiram os dois comportamentos, o que tornou a colagem de tokens um processo extremamente irritante e não confiável ao tentar portar código. O padrão com a
##
notação simplifica radicalmente isso.Pode haver outras maneiras de fazer isso. Entretanto, isso não funciona:
O GCC gera:
Perto, mas sem dados. YMMV, é claro, dependendo do pré-processador pré-padrão que você está usando. Francamente, se você está preso a um pré-processador que não está cooperando, provavelmente seria mais simples usar um pré-processador C padrão no lugar do pré-padrão (geralmente existe uma maneira de configurar o compilador adequadamente) do que gaste muito tempo tentando descobrir uma maneira de fazer o trabalho.
fonte
cpp -traditional
. Observe que não há uma resposta definitiva - depende do pré-processador que você possui.Honestamente, você não quer saber por que isso funciona. Se você sabe por que isso funciona, você se tornará o cara no trabalho que conhece esse tipo de coisa, e todos virão fazer perguntas. =)
Edit: se você realmente quer saber por que ele funciona, eu felizmente postarei uma explicação, assumindo que ninguém me bate nele.
fonte