Pergunta

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

O operador ## pré-processador fornece uma maneira de concatenar real argumentos durante a expansão macro. Se um parâmetro no texto de substituição é adjacente a uma ##, o parâmetro é substituído pelo argumento real, o ## e espaço em branco circundante são removido, e o resultado é digitalizado-re. Por exemplo, o paste macro concatena seus dois argumentos:

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

para paste(name, 1) cria o token name1.

Como e por que alguém iria usar isso no mundo real? Quais são exemplos práticos de seu uso, e há armadilhas para considerar?

Foi útil?

Solução

CrashRpt: Usando ## para converter macro cordas multi-byte para Unicode

Um uso interessante em 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 seqüência de dois bytes em vez de uma string de um byte-per-char. Isso provavelmente parece que é realmente inútil, mas eles fazem isso por uma boa razão.

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

Eles usá-lo com outra macro que retorna uma string com a data e hora.

Pondo L ao lado de um __ DATE __ lhe daria um erro de compilação.


Windows: Usando ## de genéricos cadeias de caracteres Unicode ou multi-byte

Windows usa algo como o seguinte:

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

E _T é usado em todos os lugares em código


Vários bibliotecas, usando para acessor limpo e nomes modificadoras:

Eu vi também que seja usado em código para definir acessores e modificadores:

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

Da mesma forma que você pode usar este mesmo método para quaisquer outros tipos de criação nome inteligente.


Vários bibliotecas, usá-lo para fazer várias declarações de variáveis ??de uma vez:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;

Outras dicas

Uma coisa a estar ciente de quando você está usando o token-colar ( '##') ou stringizing ( '#') pré-processamento de operadores é que você tem que usar um nível extra de engano para que eles funcionem corretamente em todos casos.

Se você não fizer isso e os itens passados ??para o operador token-colar são eles próprios macros, você poderá obter resultados que provavelmente não são o que você quer:

#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

Aqui está uma pegadinha que eu corri para quando a atualização para uma nova versão de um compilador:

A utilização desnecessária do operador token-colando (##) não é portátil e pode gerar indesejados de espaços em branco, avisos ou erros.

Quando o resultado do operador token-colar não é um símbolo de pré-processamento válido, o operador token-colar é desnecessária e possivelmente prejudiciais.

Por exemplo, pode-se tentar strings literais de construção em tempo de compilação usando o operador de token-colar:

#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, esta saída será o resultado esperado:

1+2 std::vector

Em outros compiladores, isso vai incluir espaços em branco indesejada:

1 + 2 std :: vector

versões bastante moderno do GCC (> = 3.3 ou assim) vai deixar de 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 token-colando operador quando concatenando fichas pré-processamento para os operadores de 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 GCC CPP capítulo documentação sobre concatenação tem mais informações úteis sobre o operador token-colar.

Isso é útil em todos os tipos de situações a fim de não repetir-se desnecessariamente. O que se segue é um exemplo a partir do código-fonte Emacs. Nós gostaríamos de carregar um número de funções de uma biblioteca. A função "foo" deve ser atribuído a fn_foo, e assim por diante. Nós 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);

O benefício é não ter de escrever tanto fn_XpmFreeAttributes e "XpmFreeAttributes" (e risco de erro ortográfico um deles).

A pergunta anterior sobre Stack Overflow pediu um método suave de gerar representações de seqüência de constantes de enumeração sem um monte de redigitação propenso a erros.

link

A minha resposta a essa pergunta mostrou como aplicar pouco de magia pré-processador permite que você defina sua enumeração como este (por exemplo) ...;

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

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

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

O nome da tabela de cadeia vem de colar o parâmetro macro (ou seja, a cores) para stringTable usando o operador ##. Aplicações (truques?) Como esta são onde o # e ## operadores são inestimáveis.

Eu usá-lo em programas em C a ajuda corretamente cumprir os protótipos para um conjunto de métodos que devem estar de acordo com algum tipo de convenção de chamada. De certa forma, isso pode ser usado para orientação a objetos pobre homem em linha reta C:

SCREEN_HANDLER( activeCall )

expande para algo como isto:

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

Isso reforça parametrização correta para todos "derivado" objetos quando você faz:

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

o acima em seus arquivos de cabeçalho, etc. Também é útil para a manutenção se você mesmo acontecer de querer mudar as definições e / ou adicionar métodos para os "objetos".

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

Eu uso ele para uma casa rolou assert 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 


Você pode usar colar símbolo quando você precisa para concatenar parâmetros macro com outra coisa.

Ele pode ser usado para modelos:

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

Neste caso LINKED_LIST (int) lhe daria

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

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

Eu usá-lo para adicionar prefixos personalizadas para variáveis ??definidas por macros. Então, algo como:

UNITTEST(test_name)

expande para:

void __testframework_test_name ()

O principal uso é quando você tem uma convenção de nomenclatura e você quer que seu macro para tirar proveito 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ê poderia escrever uma macro para a manipulação do ciclo de vida de 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 é a única espécie de convenção de nomenclatura isso se aplica a - quase a grande maioria das convenções de nomenclatura fazer uso de uma sub-string comum para formar os nomes. Ele me poderia funcionar nomes (como acima), ou nomes de campo, nomes de variáveis, ou então mais nada.

Um uso importante no WinCE:

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

Ao definir descrição registo pouco nós seguinte:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

E ao usar BITFMASK, basta usar:

BITFMASK(ADDR)

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

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

Ou, se o seu compilador não suporta Função e func :

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

A mensagem acima "funções" logs e mostra exatamente qual função registrada uma mensagem.

O meu C ++ sintaxe pode não ser muito correto.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top