Pergunta

Eu escrevi um pouco de C , e eu posso lê-lo bem o suficiente para ter uma idéia geral do que está fazendo, mas cada vez que eu ter encontrado uma macro que tem me jogado completamente . Eu acabar tendo que lembrar o que a macro é e substituí-lo na minha cabeça enquanto eu lia. Os que eu encontrei que eram intuitiva e fácil de entender sempre foram como pequenos mini 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 compilação de depuração ou plataforma cruzada constrói no pré-processador, mas a capacidade de definir substituições arbitrárias parece ser útil apenas para fazer uma linguagem já difícil ainda mais difícil de entender.

Por que foi um pré-processador tão complexo introduzido para C ? E alguém tem um exemplo do uso que vai me fazer entender por que ele ainda parece ser usado para outros fins que simples fins, se estilo #Depurar compilações condicionais?

Editar:

Depois de ler uma série de respostas que eu ainda simplesmente não obtê-lo. A resposta mais comum é a de código embutido. Se a palavra-chave inline não fazê-lo, em seguida, ou ele tem uma boa razão para não fazê-lo, ou a implementação precisa de conserto. Eu não entendo por que todo um mecanismo diferente é necessário que significa "realmente em linha este código" (além formam o código que está sendo escrito antes em linha foi em torno). Eu também não entendo a idéia de que foi mencionado que "se a sua muito bobo para ser colocado em uma função". Certamente qualquer pedaço de código que leva uma entrada e produz uma saída é melhor colocar em uma função. Eu acho que pode não estar recebendo-lo porque eu não estou acostumado às micro otimizações de escrever C , mas o pré-processador só se sente como uma solução complexa para alguns problemas simples.

Foi útil?

Solução

Eu acabar tendo que lembrar o que a macro é e substituí-lo na minha cabeça enquanto eu lia.

Isso parece refletir negativamente sobre a nomeação dos macros. Eu diria que você não teria para emular o pré-processador se fosse uma macro log_function_entry().

Os que eu encontrei que eram intuitiva e fácil de entender sempre foram como pequenos mini funções, então eu sempre me perguntei por que eles não estavam apenas funções.

Normalmente, eles devem ser, a menos que eles precisam para operar em parâmetros genéricos.

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

irá funcionar em qualquer tipo com um operador <.

Mais que apenas funções, 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 número de arquivo de origem e linha da macro está ligado.

Em C99, macros também permitem que você chamar funções variádicos tais 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 guarda é verdadeiro, ele envia a mensagem junto com o número do arquivo ea linha que imprimiu a mensagem. Se fosse uma chamada de função, não saberia o arquivo ea linha que você chamou-o de, e usando um vaprintf seria um pouco mais de trabalho.

Outras dicas

Este trecho resume muito bem a minha opinião sobre o assunto, por que comparam várias maneiras que macros C são usados, e como implementá-los em D.

copiado de DigitalMars.com

Voltar ao C foi inventado, compilador A tecnologia foi primitivo. a instalação de um texto pré-processador macro para a frente final foi uma maneira simples e fácil para adicionar muitas características poderosas. o o aumento do tamanho e complexidade da programas têm mostrado que estes recursos vêm com muitos inerente problemas. não D não tem um pré-processador; mas D fornece uma mais meios escaláveis ??para resolver o mesmo problemas.

Macros

macros

pré-processador adicionar características poderosas e flexibilidade para C. Mas eles têm uma desvantagem:

  • Macros não têm noção do escopo; eles são válidos do ponto de definição para o final da fonte. Eles cortar uma área através .h, código aninhada, etc. Quando #include'ing dezenas de milhares de linhas de definições de macro, torna-se problemático para evitar inadvertidas expansões de macro.
  • Macros são desconhecidos para o depurador. Tentando depurar um programa com dados simbólica é prejudicada pelo depurador só conhecendo sobre expansões macro, não as próprias macros.
  • Macros tornam impossível tokenizar código-fonte, como uma mudança mais cedo macro pode arbitrariamente refazer tokens.
  • A base puramente textual de macros leva ao uso arbitrário e inconsistente, fazendo código usando erro macros propenso. (Alguns tentativa de resolver este foi introduzido com modelos em C++.)
  • Macros ainda são usados ??para compensar os déficits na capacidade expressiva da linguagem, como para "wrappers" arquivos em torno de cabeçalho.

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

  1. A definição de constantes literais:

    • O C Preprocessor Way

      #define VALUE 5
      
    • O D Way

      const int VALUE = 5;
      
  2. Criar uma lista de valores ou bandeiras:

    • O C Preprocessor Way

      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. definição da função chamar convenções:

    • O C Preprocessor Way

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

      convenções de chamada pode ser especificado em blocos, por isso não há necessidade de alterá-lo para cada função:

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

    • O C Preprocessor Way

      Selecionar qual função usar baseado 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 permite 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;
      }
      

Há mais exemplos na DigitalMars website .

Eles são uma linguagem de programação (uma mais simples) em cima de C, então eles são úteis para fazer metaprogramming 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á escrevê-lo diretamente no C.

Eles também são muito úteis para escrever "função como" expressões que são "polimórfico" ou "sobrecarregado"; por exemplo. uma macro máximo definido como:

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

é útil para qualquer tipo numérico; e no 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 que você queria, porque você não pode sobrecarregar funções.

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

Macros permitir que alguém para modificar o comportamento do programa durante o tempo de compilação. Considere o seguinte:

  • constantes C permitem fixar o comportamento do programa em tempo de desenvolvimento
  • variáveis ??
  • C permitem modificar o comportamento do programa em tempo de execução
  • macros
  • C permitem modificar o comportamento do programa em tempo de compilação

No meio tempo de compilação que o código não utilizado não vai mesmo ir para o binário e que o processo de construção pode modificar os valores, desde que ele está integrado com o pré-processador macro. Exemplo: marca ARCO = braço (assume encaminhamento definição macro como cc -DARCH = braço)

Exemplos simples: (A partir de glibc limits.h, definir o maior valor de comprimento)

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

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

(POSIX pedido versão)

#define _POSIX_C_SOURCE 200809L

Os pedidos durante o tempo de compilação POSIX apoio de 2008. A biblioteca padrão pode suportar muitos padrões (incompatíveis), mas com esta definição, que irá fornecer os protótipos de função corretas (exemplo: getline (), não recebe (), etc.). Se a biblioteca não suporta o padrão que pode dar uma #error durante o tempo de compilação, em vez de deixar de funcionar 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 hardcode. Poderia ser alterado com -DLIBRARY_PATH = / home / user / lib, por exemplo. Se isso fosse um const char *, como você configurá-lo durante a compilação?

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

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

As grandes pedaços de texto podem que de outra forma não seria simplificada pode ser declarada (sempre em tempo de compilação). Não é possível fazer isso com funções ou constantes (em tempo de compilação).

Para evitar que as coisas realmente complicadores e evitar sugerindo estilos de codificação pobres, eu sou não vai dar um exemplo de código que compila em sistemas diferentes e incompatíveis, operando. Use seu sistema de compilação cruzada para isso, mas deve ficar claro que o pré-processador permite que sem a ajuda do sistema de construção, sem quebrar a compilação por causa de interfaces ausentes.

Finalmente, pense sobre a importância de compilação condicional em sistemas embutidos, onde a velocidade do processador e memória são limitados e sistemas são muito heterogéneas.

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

Lembre-se que macros (eo pré-processador) vêm desde os primeiros dias de C. Eles costumavam ser a única maneira de fazer inline 'funções' (porque, claro, inline é uma palavra-chave muito recente), e eles ainda são a única maneira de forçar algo para ser embutido.

Além disso, macros são a única maneira que você pode fazer esses truques como inserir o arquivo e linha em constantes de cadeia em tempo de compilação.

Hoje em dia, muitas das coisas que macros costumavam ser a única maneira de fazer são melhor tratadas através de mecanismos mais recentes. Mas eles ainda têm o seu lugar, de tempo em tempo.

Além de inlining de eficiência e compilação condicional, macros pode ser usado para elevar o nível de abstração do código de baixo nível C. C realmente não isolá-lo a partir dos pequenos detalhes da memória e da gestão de recursos e layout exato dos dados e suportes formas de ocultação de informações e outros mecanismos para o gerenciamento de grandes sistemas muito limitados. Com macros, você já não estão limitados a usar somente as construções de base na linguagem C: você pode definir suas próprias estruturas de dados e construções de codificação enquanto ainda nominalmente escrevendo C (incluindo classes e modelos!)!

macros pré-processador realmente oferecer uma linguagem Turing completo executado em tempo de compilação. Uma das impressionantes (e ligeiramente assustador) exemplos deste é superior no lado C ++: o Impulsione utiliza a biblioteca de pré-processador C99 / C ++ 98 pré-processador para construção (relativamente) construções de programação seguras que são então expandida para quaisquer declarações subjacentes e entrada de código, seja C ou C ++.

Na prática, eu recomendo relativo aos programas de pré-processamento, como último recurso, quando você não tem a liberdade para usar construções de alto nível em linguagens mais seguras. Mas às vezes é bom saber o que você pode fazer se a sua volta é contra a parede e as doninhas estão se fechando ...!

A partir estupidez computador :

Eu vi esse trecho de código em um monte de programas de jogos freeware para UNIX:

/ *
* Os valores dos bits.
* / Tablet #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 ainda mais fácil é 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 ir para todos os problemas de definição de 32 constantes? A linguagem C também tem macros parametrizadas. Tudo que você realmente precisa é:

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

De qualquer forma, gostaria de saber se cara que escreveu o código original usado uma calculadora ou apenas calculado tudo para fora no papel.

Isso é apenas um possível uso de Macros.

Um dos casos em que macros realmente brilham é quando se faz de geração de código com eles.

Eu costumava trabalhar em um velho C ++ sistema que estava usando um sistema de plugins com a sua própria maneira de passar parâmetros para o plugin (Usando um mapa-como costume estrutura). Algumas macros simples foram usadas para ser capaz de lidar com essa peculiaridade e permitiu-nos a utilizar as classes reais C ++ e funções com parâmetros normais nos plugins sem problemas demais. Todo o código a cola que está sendo gerado por macros.

Vou acrescentar ao que está já foi dito.

Porque macros trabalhar em substituições de texto que permitem fazer coisas muito úteis que não seriam possíveis de fazer uso de funções.

Aqui alguns casos em que macros podem ser realmente útil:

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

Este é um macro muito popular e frequentemente utilizado. Isto é muito útil quando você, por exemplo, necessidade para percorrer 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 acrescenta mais cinco elementos para a no decleration. O for-loop sempre percorrer todos os elementos.

As funções da biblioteca C para comparar memória e cordas são bastante feio para uso.

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 os pontos str para "Hello, world". Pessoalmente, penso que estas duas soluções parecem muito feio e confuso (especialmente !strcmp(...)).

Aqui estão dois macros puro algumas pessoas (incluindo eu) uso quando eles precisam para 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 agora escrever o código como este:

char *str = "Hello, world!";

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

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

Estes são casos foram macros são usadas para coisas funções não podem realizar. Macros não deve ser usado para substituir funções, mas eles têm outros usos bons.

Tendo em conta os comentários em sua pergunta, você não pode apreciar plenamente é que chamar uma função pode implicar uma quantidade razoável de sobrecarga. Os parâmetros e registros importantes podem ter de ser copiados para a pilha sobre a forma eo desenrolado pilha na saída. Isso foi particularmente verdadeiro para os chips mais velhos Intel. Macros deixar o programador manter a abstração de uma função (quase), mas evitou a sobrecarga caro de uma chamada de função. A palavra-chave inline é consultivo, mas o compilador não pode sempre acertar. A glória e perigo de 'C' é que geralmente você pode dobrar o compilador a sua vontade.

Em seu pão e manteiga, a aplicação no dia-a-dia de programação deste 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 chamado pelo kernel de uma operação sistema, então ele pode fazer uma enorme diferença.

funções regulares Ao contrário, você pode fazer controle de fluxo (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;
} 

É bom para inlining código e evitar a chamada de função em cima. Bem como usá-lo se você quiser mudar o comportamento posterior sem editar muitos lugares. Não é útil para coisas complexas, mas para linhas simples de código que você deseja em linha, não é ruim.

Utilizando uma manipulação texto C do pré-processador pode-se construir o equivalente C de uma estrutura de dados polimórfica. Usando esta técnica podemos construir uma caixa de ferramentas confiável de estruturas de dados primitivos que podem ser usados ??em qualquer programa C, uma vez que eles se aproveitam da sintaxe C e não as especificidades de qualquer implementação particular.

explicação detalhada sobre como utilizar macros para o gerenciamento de estrutura de dados é dado aqui - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html

Macros permitem que você se livrar de fragmentos colados contra cópia, que você não pode eliminar de qualquer outra forma.

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

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ê passar um valor de campo sob o mesmo nome em um motor de script. É este copiar-colar? 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 se esqueça de fazer o mesmo com a corda (como eu fiz), o script vai funcionar de uma forma que você não espera (na verdade, no meu exemplo, depende de você se esqueceu de mudar o nome do campo em um arquivo de script separado, mas se o script é usado para serialização, seria um erro 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 bugs. Você pode fazer um erro de digitação escrever o macro e nunca vai ver um código mimada, porque o compilador não mostra como ele olha afinal pré-processamento. Alguém poderia usar o mesmo nome (que é por isso que eu macros "libertação" o mais rápido possível com #undef). Então, usá-la sabiamente. Se você ver uma outra maneira de se livrar do código colado contra cópia (como funções), utilize esse caminho. Se você ver que se livrar do código colado cópia com macros não vale a pena o resultado, manter o código colado contra cópia.

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

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

Macros .. para quando seu & # (* $ & compilador simplesmente se recusa a linha algo.

Isso deve ser um cartaz inspirador, não?

Em toda a seriedade, google pré-processador abusar (você pode ver uma pergunta tão semelhantes como o resultado # 1). Se eu estou escrevendo uma macro que vai além da funcionalidade do assert (), eu costumo tentar ver se meu compilador seria realmente em linha uma função similar.

Outros argumentam contra o uso #if para compilação condicional .. Eles preferem que você:

if (RUNNING_ON_VALGRIND)

em vez de

#if RUNNING_ON_VALGRIND

.. para fins de depuração, já que você pode ver o if (), mas não #if em um depurador. Em seguida, mergulhar em #ifdef vs #if.

Se seus menores de 10 linhas de código, tente inline. Se ele não pode ser embutido, tentar otimizá-lo. Se a sua muito bobo para ser uma função, fazer uma macro.

Enquanto eu não sou um grande fã de macros e não tendem a escrever muito C mais, com base na minha tarefa atual, algo como isto (o que poderia, obviamente, têm alguns efeitos colaterais) é conveniente:

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

nada Agora eu não tenho escrito assim em anos, mas 'funções' como que estavam todos os códigos mais que eu mantida no início da minha carreira. Eu acho que a expansão poderia ser considerado conveniente.

Eu não vi ninguém mencionar isso para, em relação à função como macros, por exemplo:

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

Geralmente é recomendado para evitar o uso de macros quando não é necessário, por muitas razões, legibilidade sendo a principal preocupação. Assim:

Quando você deve usar estes mais uma função?

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

Agora, a pequena diferença é que macros são tratados pelo pré-processador e em linha é tratado pelo compilador, mas não há nenhuma diferença prática hoje em dia.

Quando é apropriado usá-los?

Para pequenas funções (dois ou três forros max). O objetivo é obter alguma vantagem durante o tempo de execução de um programa, como função como macros (e funções inline) são substituições de código feito durante o pré-proccessing (ou compilação em caso de in-line) e não são funções reais que vivem na memória, então não há nenhuma sobrecarga chamada de função (mais detalhes nas páginas relacionadas).

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