ti-enxame.com

Quais são as macros C úteis?

Escrevi um pouco deC, e posso lê-lo bem o suficiente para ter uma ideia geral do que está fazendo, mas toda vez que encontrei uma macro ela me jogou completamente. Eu acabo tendo que lembrar o que é a macro e substituí-la na minha cabeça enquanto eu leio. Os que eu encontrei que eram intuitivos e fáceis de entender sempre foram como pequenas funções, então eu sempre me perguntei por que eles não eram apenas funções.

Eu posso entender a necessidade de definir diferentes tipos de construção para compilações de depuração ou cross-platform no pré-processador, mas a capacidade de definir substituições arbitrárias parece ser útil apenas para tornar uma linguagem já difícil ainda mais difícil de entender.

Por que um pré-processador tão complexo foi introduzido paraC? E alguém tem um exemplo de uso que me faça entender por que ele ainda parece ser usado para propósitos diferentes de simples compilações condicionais de estilo #debug?

Editar:

Tendo lido uma série de respostas, eu ainda não entendi. A resposta mais comum é o código embutido. Se a palavra-chave inline não fizer isso, ela terá um bom motivo para não fazê-lo ou a implementação precisará ser corrigida. Eu não entendo porque um mecanismo totalmente diferente é necessário, o que significa "realmente em linha este código" (além do código que está sendo escrito antes do inline estar por perto). Eu também não entendo a idéia que foi mencionada que "se é muito bobo para ser colocado em uma função". Certamente, qualquer pedaço de código que receba uma entrada e produza uma saída é melhor colocado em uma função. Eu acho que talvez eu não esteja entendendo porque não estou acostumado com as micro otimizações de escreverC, mas o pré-processador simplesmente parece uma solução complexa para alguns problemas simples.

69
Jack Ryan

Eu acabo tendo que lembrar o que é a macro e substituí-la na minha cabeça enquanto eu leio. 

Isso parece refletir mal na nomenclatura das macros. Eu suponho que você não teria que emular o pré-processador se fosse uma macro log_function_entry()

Os que eu encontrei que eram intuitivos e fáceis de entender sempre foram como pequenas funções, então eu sempre me perguntei por que eles não eram apenas funções.

Geralmente eles devem ser, a menos que precisem operar em parâmetros genéricos. 

#define max(a,b) ((a)<(b)?(b):(a))

funcionará em qualquer tipo com um operador <.

Mais que apenas funções, as macros permitem executar operações usando os símbolos no arquivo de origem. Isso significa que você pode criar um novo nome de variável ou referenciar o arquivo de origem e o número de linha em que a macro está. 

Em C99, as macros também permitem chamar funções variadicas, como printf

#define log_message(guard,format,...) \
   if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);

log_message( foo == 7, "x %d", x)

Em que o formato funciona como printf. Se a proteção for verdadeira, ela exibe a mensagem junto com o arquivo e o número da linha que imprimiu a mensagem. Se fosse uma chamada de função, ela não saberia o arquivo e a linha de onde você a chamou, e usar uma vaprintf seria um pouco mais trabalhoso. 

53
Pete Kirkham

Este trecho resume muito bem minha opinião sobre o assunto, comparando várias maneiras que as macros C são usadas e como implementá-las em D.

copiado de DigitalMars.com

Quando o C foi inventado, a tecnologia do compilador Era primitiva. A instalação de um pré-processador de macros de texto No final da frente Foi uma maneira direta e fácil De adicionar muitos recursos poderosos. O aumento do tamanho e da complexidade dos programas Ilustram que esses recursos Vêm com muitos problemas inerentes . D não possui um pré-processador ; mas D fornece um meio escalável mais para resolver os mesmos problemas .

Macros

As macros do pré-processador adicionam recursos poderosos e flexibilidade ao C. Mas eles têm um lado negativo:

  • Macros não têm noção de escopo; eles são válidos do ponto de definição até o final da fonte. Eles cortam uma faixa em arquivos .h, código aninhado, etc. Quando #include tens tens dezenas de milhares de linhas de definições de macro, torna-se problemático evitar expansões de macro inadvertidas.
  • As macros são desconhecidas para o depurador. Tentar depurar um programa com dados simbólicos é solapado pelo depurador apenas sabendo sobre as expansões de macro, não as próprias macros.
  • As macros tornam impossível tokenizar o código-fonte, já que uma alteração de macro anterior pode refazer arbitrariamente tokens.
  • A base puramente textual das macros leva ao uso arbitrário e inconsistente, fazendo código usando macros propensas a erros. (Algumas tentativas de resolver isso foram introduzidas com modelos em C++.)
  • As macros ainda são usadas para compensar os déficits na capacidade expressiva da linguagem, como os "wrappers" em torno dos arquivos de cabeçalho.

Aqui está uma enumeração dos usos comuns para macros e o recurso correspondente em D:

  1. Definindo constantes literais:

    • O caminho do pré-processador C

      #define VALUE 5
      
    • O D Way

      const int VALUE = 5;
      
  2. Criando uma lista de valores ou sinalizadores:

    • O caminho do pré-processador C

      int flags:
      #define FLAG_X  0x1
      #define FLAG_Y  0x2
      #define FLAG_Z  0x4
      ...
      flags |= FLAG_X;
      
    • O D Way

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  3. Definindo convenções de chamada de função:

    • O caminho do pré-processador C

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • O D Way

      As convenções de chamada podem ser especificadas em blocos, portanto, não é necessário alterá-lo para todas as funções:

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  4. Programação genérica simples:

    • O caminho do pré-processador C

      Selecionando qual função usar com base na substituição de texto:

      #ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #else
      int getValueA(char *p);
      #define getValue getValueA
      #endif
      
    • O D Way

      D ativa declarações de símbolos que são aliases de outros símbolos:

      version (UNICODE)
      {
          int getValueW(wchar[] p);
          alias getValueW getValue;
      }
      else
      {
          int getValueA(char[] p);
          alias getValueA getValue;
      }
      

Existem mais exemplos no site DigitalMars .

17
Brad Gilbert

Eles são uma linguagem de programação (mais simples) no topo de C, então eles são úteis para fazer metaprogramação em tempo de compilação ... em outras palavras, você pode escrever código de macro que gera código C em menos linhas e tempo que levará escrevendo diretamente em C.

Eles também são muito úteis para escrever expressões "function like" que são "polimórficas" ou "sobrecarregadas"; por exemplo. uma macro máxima definida como:

#define max(a,b) ((a)>(b)?(a):(b))

é útil para qualquer tipo numérico; e em C você não poderia escrever:

int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) {return a>b?a:b;}
double max(double a, double b) {return a>b?a:b;}
...

mesmo se você quisesse, porque você não pode sobrecarregar funções.

E para não mencionar a compilação condicional e incluindo arquivos (que também fazem parte da linguagem de macro) ...

15
fortran

As macros permitem que alguém modifique o comportamento do programa durante o tempo de compilação. Considere isto:

  • Constantes C permitem consertar o comportamento do programa em tempo de desenvolvimento
  • Variáveis ​​C permitem modificar o comportamento do programa no tempo de execução
  • Macros C permitem modificar o comportamento do programa em tempo de compilação

No momento da compilação, significa que o código não utilizado não vai mesmo para o binário e que o processo de compilação pode modificar os valores, desde que esteja integrado ao pré-processador da macro. Exemplo: make Arch = arm (assume a definição da macro de encaminhamento como cc -DARCH = arm)

Exemplos simples: (De glibc limits.h, defina o maior valor de long)

#if __WORDSIZE == 64
#define LONG_MAX 9223372036854775807L
#else
#define LONG_MAX 2147483647L
#endif

Verifica (usando o #define __WORDSIZE) em tempo de compilação se estamos compilando para 32 ou 64 bits. Com um conjunto de ferramentas multilib, os parâmetros -m32 e -m64 podem alterar automaticamente o tamanho do bit.

(Pedido de versão POSIX)

#define _POSIX_C_SOURCE 200809L

Pedidos durante o tempo de compilação Suporte POSIX 2008. A biblioteca padrão pode suportar muitos padrões (incompatíveis), mas com essa definição, ela fornecerá os protótipos de função corretos (exemplo: getline (), no gets (), etc.). Se a biblioteca não suportar o padrão, pode resultar em # erro durante o tempo de compilação, em vez de travar durante a execução, por exemplo.

(caminho codificado)

#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif

Define, durante o tempo de compilação, um diretório de hardcode. Pode ser alterado com -DLIBRARY_PATH =/home/user/lib, por exemplo. Se isso fosse um const char *, como você o configuraria durante a compilação?

(pthread.h, definições complexas no tempo de compilação)

# define PTHREAD_MUTEX_INITIALIZER \
  { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

Grandes pedaços de texto que de outra forma não seriam simplificados podem ser declarados (sempre em tempo de compilação). Não é possível fazer isso com funções ou constantes (em tempo de compilação).

Para evitar realmente complicar as coisas e evitar sugerir estilos de codificação ruins, não darei um exemplo de código que compila em sistemas operacionais diferentes e incompatíveis. Use o seu sistema cross build para isso, mas deve ficar claro que o pré-processador permite isso sem a ajuda do sistema de compilação, sem quebrar a compilação por causa de interfaces ausentes.

Finalmente, pense na importância da compilação condicional em sistemas embarcados, em que a velocidade e a memória do processador são limitadas e os sistemas são muito heterogêneos.

Agora, se você perguntar, é possível substituir todas as definições de constantes de macro e chamadas de função com definições adequadas? A resposta é sim, mas isso não fará simplesmente desaparecer a necessidade de alterar o comportamento do programa durante a compilação. O pré-processador ainda seria necessário.

12
hdante

Lembre-se que as macros (e o pré-processador) vêm dos primeiros dias de C. Elas costumavam ser a ÚNICA maneira de fazer 'funções' inline (porque, é claro, inline é uma palavra-chave muito recente), e elas ainda são Única maneira de forçar algo a ser embutido. 

Além disso, as macros são a única maneira de fazer truques como inserir o arquivo e inserir as constantes de string no tempo de compilação.

Hoje em dia, muitas das coisas que as macros costumavam ser a única maneira de fazer são mais bem tratadas por meio de mecanismos mais recentes. Mas eles ainda têm seu lugar, de tempos em tempos.

11
Michael Kohne

Além de inline para eficiência e compilação condicional, macros podem ser usadas para elevar o nível de abstração do código C de baixo nível. C realmente não o isola dos detalhes básicos da memória e do gerenciamento de recursos e do layout exato dos dados, e suporta formas muito limitadas de ocultação de informações e outros mecanismos para gerenciar sistemas grandes. Com as macros, você não está mais limitado a usar apenas as construções base na linguagem C: você pode definir suas próprias estruturas de dados e construções de codificação (incluindo classes e modelos!) Enquanto ainda escreve nominalmente C! 

As macros de pré-processamento, na verdade, oferecem uma linguagem Turing-complete executada em tempo de compilação. Um dos exemplos impressionantes (e ligeiramente assustadores) disso está no lado do C++: a biblioteca Boost Preprocessor usa o pré-processador C99 / C++ 98 para construir (relativamente) segurança construções de programação que são então expandidas para quaisquer declarações e códigos subjacentes que você insira, seja C ou C++. 

Na prática, eu recomendaria a programação do pré-processador como último recurso, quando você não tem a liberdade para usar construções de alto nível em idiomas mais seguros. Mas às vezes é bom saber o que você pode fazer se suas costas estão contra a parede e as doninhas estão se aproximando ...! 

8
Pontus Gagge

De Computer Stupidities :

Eu vi este trecho de código em muitos programas de jogos freeware para UNIX:

/ *
* Valores de bits.
* /
#define BIT_0 1
#define BIT_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16
#define BIT_5 32
#define BIT_6 64
#define BIT_7 128
#define BIT_8 256
#define BIT_9 512
#define BIT_10 1024
#define BIT_11 2048
#define BIT_12 4096
#define BIT_13 8192
#define BIT_14 16384
#define BIT_15 32768
#define BIT_16 65536
#define BIT_17 131072
#define BIT_18 262144
#define BIT_19 524288
#define BIT_20 1048576
#define BIT_21 2097152
#define BIT_22 4194304
#define BIT_23 8388608
#define BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108864
#define BIT_27 134217728
#define BIT_28 268435456
#define BIT_29 536870912
#define BIT_30 1073741824
#define BIT_31 2147483648 

Uma maneira muito mais fácil de conseguir isso é:

#define BIT_0 0x00000001
#define BIT_1 0x00000002
#define BIT_2, 0x00000004
#define BIT_3, 0x00000008
#define BIT_4, 0x00000010
...
#define BIT_28 0x10000000
#define BIT_29 0x20000000
#define BIT_30 0x40000000
#define BIT_31 0x80000000 

Uma maneira mais fácil ainda é deixar o compilador fazer os cálculos:

#define BIT_0 (1)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define BIT_3 (1 << 3)
#define BIT_4 (1 << 4)
...
#define BIT_28 (1 << 28)
#define BIT_29 (1 << 29)
#define BIT_30 (1 << 30)
#define BIT_31 (1 << 31) 

Mas por que todo o problema de definir 32 constantes? A linguagem C também possui macros parametrizadas. Tudo o que você realmente precisa é:

#define BIT (x) (1 << (x))

De qualquer forma, eu me pergunto se cara que escreveu o código original usou uma calculadora ou apenas computou tudo no papel.

Isso é apenas um uso possível de macros.

7
Niet the Dark Absol

Eu adicionarei ao que já foi dito.

Como as macros funcionam em substituições de texto, elas permitem fazer coisas muito úteis que não seriam possíveis usando funções.

Aqui alguns casos em que as macros podem ser realmente úteis:

/* Get the number of elements in array 'A'. */
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

Essa é uma macro muito popular e usada com frequência. Isso é muito útil quando, por exemplo, você precisa fazer uma iteração em um array.

int main(void)
{
    int a[] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < ARRAY_LENGTH(a); ++i) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return 0;
}

Aqui não importa se outro programador adiciona mais cinco elementos a a na decleration. O for-loop irá sempre iterar através de todos os elementos.

As funções da biblioteca C para comparar a memória e as strings são muito feias de usar.

Você escreve:

char *str = "Hello, world!";

if (strcmp(str, "Hello, world!") == 0) {
    /* ... */
}

ou

char *str = "Hello, world!";

if (!strcmp(str, "Hello, world!")) {
    /* ... */
}

Para verificar se str aponta para "Hello, world". Eu pessoalmente acho que ambas as soluções parecem muito feias e confusas (especialmente !strcmp(...)). 

Aqui estão duas macros legais que algumas pessoas (incluindo eu) usam quando precisam comparar strings ou memória usando strcmp/memcmp:

/* Compare strings */
#define STRCMP(A, o, B) (strcmp((A), (B)) o 0)

/* Compare memory */
#define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)

Agora você pode escrever o código assim:

char *str = "Hello, world!";

if (STRCMP(str, ==, "Hello, world!")) {
    /* ... */
}

Aqui está a intenção muito mais clara!

Estes são casos em que macros são usadas para coisas que funções não podem realizar. As macros não devem ser usadas para substituir funções, mas elas têm outros bons usos.

5
wefwefa3

Um dos casos em que as macros realmente brilham é ao fazer a geração de código com elas.

Eu costumava trabalhar em um sistema C++ antigo que estava usando um sistema de plug-in com seu próprio caminho para passar parâmetros para o plug-in (usando uma estrutura semelhante a um mapa customizado). Algumas macros simples foram usadas para lidar com essa peculiaridade e nos permitiram usar classes e funções C++ reais com parâmetros normais nos plugins sem muitos problemas. Todo o código de cola sendo gerado por macros.

5
Julien Roncaglia

Considerando os comentários em sua pergunta, você pode não perceber que chamar uma função pode acarretar uma quantidade razoável de sobrecarga. Os parâmetros e registradores de chaves podem ter que ser copiados para a pilha no caminho e a pilha desenrolada na saída. Isto foi particularmente verdadeiro para os chips Intel mais antigos. As macros permitem que o programador mantenha a abstração de uma função (quase), mas evita a sobrecarga dispendiosa de uma chamada de função. A palavra-chave inline é consultiva, mas o compilador pode nem sempre acertar. A glória e o perigo de 'C' é que normalmente você pode dobrar o compilador à sua vontade.

No seu pão com manteiga, a programação diária de aplicativos desse tipo de micro-otimização (evitando chamadas de função) é geralmente pior do que inútil, mas se você estiver escrevendo uma função de tempo crítico chamada pelo kernel de um sistema operacional, então pode fazer uma diferença enorme.

4
Charles E. Grant

É bom para inlining código e evitando sobrecarga de chamada de função. Bem como usá-lo se você quiser alterar o comportamento mais tarde sem editar muitos lugares. Não é útil para coisas complexas, mas para linhas simples de código que você deseja embutir, não é ruim.

3
Arkaitz Jimenez

Ao contrário das funções regulares, você pode fazer o fluxo de controle (if, while, for, ...) em macros. Aqui está um exemplo:

#include <stdio.h>

#define Loop(i,x) for(i=0; i<x; i++)

int main(int argc, char *argv[])
{
    int i;
    int x = 5;
    Loop(i, x)
    {
        printf("%d", i); // Output: 01234
    } 
    return 0;
} 
3
alexpinho98

As macros permitem que você se livre de fragmentos copiados e colados, que você não pode eliminar de nenhuma outra forma

Por exemplo (o código real, sintaxe do compilador do VS 2010):

for each (auto entry in entries)
{
        sciter::value item;
        item.set_item("DisplayName",    entry.DisplayName);
        item.set_item("IsFolder",       entry.IsFolder);
        item.set_item("IconPath",       entry.IconPath);
        item.set_item("FilePath",       entry.FilePath);
        item.set_item("LocalName",      entry.LocalName);
        items.append(item);
    }

Este é o lugar onde você passa um valor de campo com o mesmo nome para um mecanismo de script. Isso é copiado e colado? Sim. DisplayName é usado como uma string para um script e como um nome de campo para o compilador. Isso é ruim? Sim. Se você refatorar seu código e renomear LocalName para RelativeFolderName (como eu fiz) e esquecer de fazer o mesmo com a string (como eu fiz), o script funcionará de uma forma que você não espera (na verdade, no meu exemplo depende se você esqueceu de renomear o campo em um arquivo de script separado, mas se o script é usado para serialização, seria um erro de 100%).

Se você usar uma macro para isso, não haverá espaço para o erro:

for each (auto entry in entries)
{
#define STR_VALUE(arg) #arg
#define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field)
        sciter::value item;
        SET_ITEM(DisplayName);
        SET_ITEM(IsFolder);
        SET_ITEM(IconPath);
        SET_ITEM(FilePath);
        SET_ITEM(LocalName);
#undef SET_ITEM
#undef STR_VALUE
        items.append(item);
    }

Infelizmente, isso abre uma porta para outros tipos de erros. Você pode criar um erro de digitação ao escrever a macro e nunca verá um código corrompido, porque o compilador não mostra como fica depois de todo o pré-processamento. Alguém poderia usar o mesmo nome (é por isso que eu "libero" as macros o mais rápido possível com #undef). Então, use sabiamente. Se você vir outra maneira de se livrar do código copiado (como funções), use dessa maneira. Se você perceber que livrar-se do código copiado e copiado com macros não vale o resultado, mantenha o código copiado.

2
noober

Aproveitando a manipulação de texto do pré-processador C, pode-se construir o equivalente C de uma estrutura de dados polimórficos. Usando essa técnica, podemos construir uma caixa de ferramentas confiável de estruturas de dados primitivas que podem ser usadas em qualquer programa C, pois elas aproveitam a sintaxe C e não as especificidades de qualquer implementação específica.

Explicação detalhada sobre como usar macros para gerenciar a estrutura de dados é fornecida aqui - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html

2
coredump

Uma das razões óbvias é que, usando uma macro, o código será expandido em tempo de compilação e você receberá uma pseudo chamada de função sem a sobrecarga de chamada. 

Caso contrário, você também pode usá-lo para constantes simbólicas, para que você não precise editar o mesmo valor em vários locais para alterar uma pequena coisa.

1
sykora

Macros .. para quando o seu & # (* $ & compilador apenas se recusar a inserir algo.

Isso deveria ser um pôster motivacional, não?

Com toda a seriedade, google Preprocessor abuse (você pode ver uma pergunta semelhante SO como o resultado número 1). Se eu estou escrevendo uma macro que vai além da funcionalidade de assert (), eu costumo tentar ver se o meu compilador realmente inline uma função similar.

Outros irão argumentar contra o uso de #if para compilação condicional .. eles preferem você:

if (RUNNING_ON_VALGRIND)

ao invés de

#if RUNNING_ON_VALGRIND

.. para propósitos de depuração, desde que você pode ver o if () mas não #if em um debugger. Então nós mergulhamos em #ifdef vs #if.

Se for inferior a 10 linhas de código, tente inline-lo. Se não for possível, tente otimizá-lo. Se for muito bobo para ser uma função, faça uma macro.

0
Tim Post

Embora eu não seja um grande fã de macros e não tenha mais tendência a escrever muito C, com base nas minhas tarefas atuais, algo assim (o que obviamente poderia ter alguns efeitos colaterais) é conveniente:

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

Agora eu não escrevo nada parecido há anos, mas 'funções' como essa eram códigos que eu mantinha no começo da minha carreira. Eu acho que a expansão poderia ser considerada conveniente.

0
itsmatt

Eu não vi ninguém mencionando isso, sobre funções como macros, por exemplo:

#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

Geralmente é recomendado evitar o uso de macros quando não for necessário, por muitos motivos, sendo a legibilidade a principal preocupação. Assim:

Quando você deve usá-los em uma função?

Quase nunca, desde que há uma alternativa mais legível que é inline, veja https://www.greenend.org.uk/rjk/tech/inline.html Ou http: // www .cplusplus.com/articles/2LywvCM9/ (o segundo link é uma página em C++, mas o ponto é aplicável aos compiladores c tanto quanto eu sei).

Agora, a pequena diferença é que as macros são manipuladas pelo pré-processador e o inline é tratado pelo compilador, mas não há diferença prática nos dias de hoje.

quando é apropriado usá-los?

Para pequenas funções (dois ou três liners max). O objetivo é ganhar alguma vantagem durante o tempo de execução de um programa, pois funções como macros (e funções embutidas) são substituições de código feitas durante o pré-processamento (ou compilação no caso de inline) e não são funções reais que vivem na memória, Portanto, não há sobrecarga de chamada de função (mais detalhes nas páginas vinculadas).

0
äli