Criação de macro C com ## e __LINE__ (concatenação de token com macro de posicionamento)

107

Eu quero criar uma macro C que cria uma função com um nome baseado no número da linha. Achei que poderia fazer algo como (a função real teria declarações entre colchetes):

#define UNIQUE static void Unique_##__LINE__(void) {}

Que eu esperava que se expandisse para algo como:

static void Unique_23(void) {}

Isso não funciona. Com a concatenação de token, as macros de posicionamento são tratadas literalmente, acabando por se expandir para:

static void Unique___LINE__(void) {}

Isso é possível fazer?

(Sim, há um motivo real pelo qual quero fazer isso, não importa o quão inútil pareça).

DD.
fonte
Acho que você pode fazer isso funcionar com expansão macro indireta .
Ben Stiglitz de
4
possível duplicata de Como concatenar duas vezes com o pré-processador C e expandir uma macro como em "arg ## _ ## MACRO"? O mesmo vale para qualquer macro __LINE__(embora esse seja um caso de uso comum.
Ciro Santilli 郝海东 冠状 病 六四 事件 事件 法轮功

Respostas:

176

O problema é que, quando você tem uma substituição de macro, o pré-processador só irá expandir as macros recursivamente se nem o operador de stringizing #nem o operador de colagem de token ##forem aplicados a ela. Então, você tem que usar algumas camadas extras de indireção, você pode usar o operador token-colando com um argumento expandido recursivamente:

#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define UNIQUE static void TOKENPASTE2(Unique_, __LINE__)(void) {}

Então, __LINE__é expandido para o número da linha durante a expansão de UNIQUE(uma vez que não está envolvido com #ou ##), e então a colagem do token acontece durante a expansão de TOKENPASTE.

Também deve ser observado que existe também a __COUNTER__macro, que se expande para um novo número inteiro a cada vez que é avaliada, caso seja necessário ter várias instanciações da UNIQUEmacro na mesma linha. Observação: __COUNTER__é compatível com MS Visual Studio, GCC (desde V4.3) e Clang, mas não é o padrão C.

Adam Rosenfield
fonte
3
Receio que isso não funcione com GNU cpp. TOKENPASTE usa LINE como um literal. TOKENPASTE (Unique_, LINE ) expande para Unique___LINE__
DD.
3
@DD: Nossa, consertado agora. Precisa de 2 camadas de indireção, não 1.
Adam Rosenfield,
A __COUNTER__macro não funcionou para mim no gcc; embora __LINE__um tenha funcionado como anunciado.
Tyler
2
Um pouco de informação extra para qualquer um que tente o COUNTER , de acordo com msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx é uma macro específica da Microsoft.
Elva
3
Alguma explicação de por que você precisa de 2 níveis de indireção? Eu tentei com apenas um, ausente de # e ##, e isso não expande no VS2017. Aparentemente, o mesmo se aplica ao GCC. Mas se você adicionar um segundo nível de indireção, ele se expandirá. Magia?
Gabe Halsmer
-2

O GCC não requer "empacotamento" (ou realização) a menos que o resultado precise ser "sequenciado". Gcc tem recursos, mas TUDO pode ser feito com C versão 1 simples (e alguns argumentam que Berkeley 4.3 C é muito mais rápido que vale a pena aprender como usá-lo).

** Clang (llvm) NÃO FAZ ESPAÇO EM BRANCO CORRETAMENTE para expansão de macro - ele adiciona espaços em branco (o que certamente destrói o resultado como sendo um identificador C para pré-processamento posterior) **, clang simplesmente não faz # ou * expansão de macro como um pré-processador C é esperado por décadas. O exemplo principal é a compilação do X11, a macro "Concat3" está quebrada, seu resultado agora é o Identificador C MISNAMED, que obviamente falha na construção. e estou começando a achar que as falhas de build são a profissão deles.

Acho que a resposta aqui é "novo C que quebra os padrões é C ruim", esses hacks sempre optam por (desmontar os namespaces), eles mudam os padrões sem motivo, mas não "melhoram o C" (exceto os seus próprios, digamos: que i digamos que é uma engenhoca feita para explicar por que eles escapam com toda a quebra que ninguém ainda os responsabilizou).


Não é um problema que os pré-processadores C anteriores não suportavam UNIq_ () __ porque eles suportavam #pragma que permite "hackeamento de marca do compilador no código ser sinalizado como hackeamento" e também funcionam tão bem SEM afetar os padrões: defaults é uma quebra de wonton inútil, e assim como alterar o que uma função faz enquanto usa o mesmo nome (namespace clobbering) é ... malware na minha opinião

ninguém desvalorizado
fonte