Quais são as aplicações do operador de pré-processador ## e as dicas a serem consideradas?

87

Como mencionado em muitas das minhas perguntas anteriores, estou trabalhando com K&R e atualmente estou no pré-processador. Uma das coisas mais interessantes - algo que eu nunca soube antes de qualquer uma de minhas tentativas anteriores de aprender C - é o ##operador de pré - processador. De acordo com K&R:

O operador pré-processador ## fornece uma maneira de concatenar os argumentos reais durante a expansão da macro. Se um parâmetro no texto de substituição for adjacente a a ##, o parâmetro será substituído pelo argumento real, o ##e o espaço em branco ao redor serão removidos e o resultado será verificado novamente. Por exemplo, a macro paste concatena seus dois argumentos:

#define paste(front, back) front ## back

então paste(name, 1)cria o token name1.

Como e por que alguém usaria isso no mundo real? Quais são os exemplos práticos de seu uso e há algumas dicas a serem consideradas?

John Rudy
fonte

Respostas:

47

CrashRpt: usando ## para converter strings de macro de bytes múltiplos em Unicode

Um uso interessante no CrashRpt (biblioteca de relatórios de falhas) é o seguinte:

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

Aqui, eles querem usar uma string de dois bytes em vez de uma string de um byte por caractere. Isso provavelmente parece ser realmente inútil, mas eles fazem isso por um bom motivo.

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

Eles o usam com outra macro que retorna uma string com a data e a hora.

Colocar Lpróximo a a __ DATE __resultaria em um erro de compilação.


Windows: usando ## para strings Unicode ou multibyte genéricas

O Windows usa algo como o seguinte:

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

E _Té usado em todo o código


Várias bibliotecas, usando para limpar nomes de acessadores e modificadores:

Eu também vi isso ser usado em código para definir acessadores e modificadores:

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

Da mesma forma, você pode usar esse mesmo método para qualquer outro tipo de criação de nome inteligente.


Várias bibliotecas, usando-o para fazer várias declarações de variáveis ​​ao mesmo tempo:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;
Brian R. Bondy
fonte
3
Como você pode concatenar literais de string em tempo de compilação, pode reduzir a expressão BuildDate para std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); e criar implicitamente a string inteira de uma vez.
user666412
49

Uma coisa que você deve estar ciente ao usar os operadores de pré-processamento token-paste (' ##') ou stringizing (' #') é que você precisa usar um nível extra de indireção para que funcionem corretamente em todos os casos.

Se você não fizer isso e os itens passados ​​para o operador de colagem de token forem macros, você obterá resultados que provavelmente não são o que deseja:

#include <stdio.h>

#define STRINGIFY2( x) #x
#define STRINGIFY(x) STRINGIFY2(x)
#define PASTE2( a, b) a##b
#define PASTE( a, b) PASTE2( a, b)

#define BAD_PASTE(x,y) x##y
#define BAD_STRINGIFY(x) #x

#define SOME_MACRO function_name

int main() 
{
    printf( "buggy results:\n");
    printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));

    printf( "\n" "desired result:\n");
    printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
}

A saída:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21
Michael Burr
fonte
1
Para obter uma explicação desse comportamento do pré-processador, consulte stackoverflow.com/questions/8231966/…
Adam Davis
@MichaelBurr estava lendo sua resposta e tenho uma dúvida. Por que esta LINHA está imprimindo o número da linha?
AJUDA PLZ
3
@AbhimanyuAryan: Não tenho certeza se é isso que você está perguntando, mas __LINE__é um nome de macro especial que é substituído pelo pré-processador com o número da linha atual do arquivo de origem.
Michael Burr
Seria legal se as especificações de idioma pudessem ser citadas / vinculadas, como aqui
Antonio
14

Aqui está uma pegadinha que encontrei ao atualizar para uma nova versão de um compilador:

O uso desnecessário do operador token-paste ( ##) não é portátil e pode gerar espaços em branco indesejados, avisos ou erros.

Quando o resultado do operador token-colando não é um token de pré-processador válido, o operador token-colando é desnecessário e possivelmente prejudicial.

Por exemplo, pode-se tentar construir literais de string em tempo de compilação usando o operador token-colando:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a##+##b)
#define NS(a, b) STRINGIFY(a##::##b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

Em alguns compiladores, isso produzirá o resultado esperado:

1+2 std::vector

Em outros compiladores, isso incluirá espaços em branco indesejados:

1 + 2 std :: vector

Versões razoavelmente modernas do GCC (> = 3.3 ou mais) não conseguirão compilar este código:

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token

A solução é omitir o operador token-paste ao concatenar tokens de pré-processador para operadores C / C ++:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a+b)
#define NS(a, b) STRINGIFY(a::b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

O capítulo da documentação do GCC CPP sobre concatenação tem informações mais úteis sobre o operador de colagem de token.

bk1e
fonte
Obrigado - eu não estava ciente disso (mas eu não uso muito esses operadores de pré-processamento ...).
Michael Burr de
3
É chamado de operador de "colagem de token" por um motivo - a intenção é terminar com um único token quando terminar. Boa redação.
Mark Ransom
Quando o resultado do operador token-pasteing não é um token de pré-processador válido, o comportamento é indefinido.
alecov
Mudanças de linguagem como flutuantes hexadecimais, ou (em C ++) separadores de dígitos e literais definidos pelo usuário, mudam continuamente o que constitui um "token de pré-processamento válido", portanto, nunca abuse assim! Se você tiver que separar os tokens (da linguagem adequada), soletre-os como dois tokens separados e não dependa de interações acidentais entre a gramática do pré-processador e a linguagem adequada.
Kerrek SB
6

Isso é útil em todos os tipos de situações, para não se repetir desnecessariamente. A seguir está um exemplo do código-fonte do Emacs. Gostaríamos de carregar várias funções de uma biblioteca. A função "foo" deve ser atribuída fn_fooe assim por diante. Definimos a seguinte macro:

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

Podemos então usá-lo:

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

A vantagem é não ter que escrever ambos fn_XpmFreeAttributese "XpmFreeAttributes"(e correr o risco de errar a grafia de um deles).

Vebjorn Ljosa
fonte
4

Uma pergunta anterior no Stack Overflow pedia um método suave de geração de representações de string para constantes de enumeração sem muitos redigitações propensas a erros.

Ligação

Minha resposta a essa pergunta mostrou como aplicar pouca magia de pré-processador permite definir sua enumeração assim (por exemplo) ...;

ENUM_BEGIN( Color )
  ENUM(RED),
  ENUM(GREEN),
  ENUM(BLUE)
ENUM_END( Color )

... Com o benefício de que a expansão da macro não apenas define a enumeração (em um arquivo .h), ela também define uma matriz correspondente de strings (em um arquivo .c);

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

O nome da tabela de strings vem da colagem do parâmetro macro (ou seja, Cor) em StringTable usando o operador ##. Aplicativos (truques?) Como este são onde os operadores # e ## são inestimáveis.

Bill Forster
fonte
3

Você pode usar a colagem de token quando precisar concatenar parâmetros de macro com outra coisa.

Pode ser usado para modelos:

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

Neste caso, LINKED_LIST (int) daria a você

struct list_int {
int value;
struct list_int *next;
};

Da mesma forma, você pode escrever um modelo de função para travessia de lista.

qrdl
fonte
2

Eu o uso em programas C para ajudar a impor corretamente os protótipos para um conjunto de métodos que devem estar em conformidade com algum tipo de convenção de chamada. De certa forma, isso pode ser usado para orientação a objetos do homem pobre em C direto:

SCREEN_HANDLER( activeCall )

se expande para algo assim:

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

Isso impõe a parametrização correta para todos os objetos "derivados" quando você:

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

o acima em seus arquivos de cabeçalho, etc. Também é útil para manutenção, se você desejar alterar as definições e / ou adicionar métodos aos "objetos".

Jeff alto
fonte
2

SGlib usa ## para basicamente falsificar modelos em C. Como não há sobrecarga de função, ## é usado para colar o nome do tipo nos nomes das funções geradas. Se eu tivesse um tipo de lista chamado list_t, obteria funções nomeadas como sglib_list_t_concat e assim por diante.


fonte
2

Eu o uso para uma declaração inicial em um compilador C não padrão para incorporado:



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 


c0m4
fonte
3
Suponho que você queira dizer com 'não padrão' que o compilador não colou string, mas colou token - ou teria funcionado mesmo sem ##?
PJTraill de
1

Eu o uso para adicionar prefixos personalizados a variáveis ​​definidas por macros. Então, algo como:

UNITTEST(test_name)

expande para:

void __testframework_test_name ()
John Millikin
fonte
1

O uso principal é quando você tem uma convenção de nomenclatura e deseja que sua macro tire vantagem dessa convenção de nomenclatura. Talvez você tenha várias famílias de métodos: image_create (), image_activate () e image_release () também file_create (), file_activate (), file_release () e mobile_create (), mobile_activate () e mobile_release ().

Você pode escrever uma macro para lidar com o ciclo de vida do objeto:

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

Claro, uma espécie de "versão mínima de objetos" não é o único tipo de convenção de nomenclatura à qual isso se aplica - quase a grande maioria das convenções de nomenclatura usa uma subcadeia comum para formar os nomes. Poderia me nomes de função (como acima), ou nomes de campo, nomes de variáveis ​​ou quase qualquer outra coisa.

Mcherm
fonte
1

Um uso importante no WinCE:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

Ao definir a descrição do bit de registro, fazemos o seguinte:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

E ao usar o BITFMASK, basta usar:

BITFMASK(ADDR)
Keshava GN
fonte
0

É muito útil para registro. Você pode fazer:

#define LOG(msg) log_msg(__function__, ## msg)

Ou, se seu compilador não suportar função e função :

#define LOG(msg) log_msg(__file__, __line__, ## msg)

As "funções" acima registram mensagens e mostram exatamente qual função registrou uma mensagem.

Minha sintaxe C ++ pode não estar totalmente correta.

ya23
fonte
1
O que você estava tentando fazer com isso? Funcionaria igualmente bem sem o "##", uma vez que não há necessidade de colar token "," para "msg". Você estava tentando restringir a mensagem? Além disso, FILE e LINE devem estar em maiúsculas, não em minúsculas.
bk1e
Você está certo mesmo. Preciso encontrar o script original para ver como ## foi usado. Que vergonha, nada de biscoito hoje!
ya23