Pré-processador C padrão
$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)
extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"
extern void mine_3(char *x);
$
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'.
Após a identificação dos argumentos para a invocação de uma macro semelhante à função, ocorre a substituição de argumentos. Um parâmetro na lista de substituição, a menos que seja precedido por um token de pré #
- ##
processamento ou seguido por um ##
token de pré - processamento (veja abaixo), é substituído pelo argumento correspondente depois que todas as macros nele contidas foram expandidas. Antes de serem substituídos, os tokens de pré-processamento de cada argumento são completamente substituídos por macro, como se tivessem formado o restante do arquivo de pré-processamento; nenhum outro tokens de pré-processamento está disponível.
Na invocação NAME(mine)
, o argumento é 'meu'; é totalmente expandido para 'meu'; é então substituído na cadeia de substituição:
EVALUATOR(mine, VARIABLE)
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:
PASTER(mine, 3)
A operação disso é coberta por outras regras (6.10.3.3 'O operador ##'):
Se, na lista de substituição de uma macro semelhante à função, um parâmetro for imediatamente precedido ou seguido por um ##
token de pré processamento, o parâmetro será substituído pela sequência de token de pré-processamento do argumento correspondente; [...]
Para chamadas de macro semelhantes a objeto e a função, antes que a lista de substituição seja reexaminada para que mais nomes de macro sejam substituídos, cada instância de um ##
token de pré processamento na lista de substituição (não de um argumento) é excluída e o token de pré-processamento anterior é concatenado com o seguinte token de pré-processamento.
Portanto, a lista de substituição contém x
seguido por ##
e também ##
seguido por y
; então nós temos:
mine ## _ ## 3
e eliminar os ##
tokens e concatenar os tokens de ambos os lados combina 'mine' com '_' e '3' para produzir:
mine_3
Este é o resultado desejado.
Se olharmos para a pergunta original, o código foi (adaptado para usar 'mine' em vez de 'some_function'):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
O argumento para NAME é claramente 'meu' e é totalmente expandido.
Seguindo as regras de 6.10.3.3, encontramos:
mine ## _ ## VARIABLE
que, quando os ##
operadores são eliminados, mapeia para:
mine_VARIABLE
exatamente como relatado na pergunta.
Pré-processador C tradicional
Robert Rüger pergunta :
Existe alguma maneira de fazer isso com o pré-processador C tradicional que não possui o operador de colagem de token ##
?
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
):
#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
Observe que não há um espaço entre fun,
e VARIABLE
- isso é importante porque, se presente, ele é copiado para a saída e você acaba com mine_ 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:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_3(char *x);
Com o Clang do XCode 8.2.1, recebo:
# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2
extern void mine _ 3(char *x);
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:
#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
O GCC gera:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
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.