Sempre vi exemplos e casos em que usar macro é melhor do que usar função.
Alguém poderia me explicar com um exemplo a desvantagem de uma macro em relação a uma função?
Sempre vi exemplos e casos em que usar macro é melhor do que usar função.
Alguém poderia me explicar com um exemplo a desvantagem de uma macro em relação a uma função?
Respostas:
As macros estão sujeitas a erros porque dependem da substituição textual e não executam a verificação de tipo. Por exemplo, esta macro:
#define square(a) a * a
funciona bem quando usado com um inteiro:
square(5) --> 5 * 5 --> 25
mas faz coisas muito estranhas quando usado com expressões:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
Colocar os argumentos entre parênteses ajuda, mas não elimina completamente esses problemas.
Quando as macros contêm várias instruções, você pode ter problemas com construções de fluxo de controle:
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
A estratégia usual para consertar isso é colocar as instruções dentro de um loop "do {...} while (0)".
Se você tiver duas estruturas que contenham um campo com o mesmo nome, mas semânticas diferentes, a mesma macro pode funcionar em ambos, com resultados estranhos:
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
Finalmente, macros podem ser difíceis de depurar, produzindo erros de sintaxe estranhos ou erros de tempo de execução que você precisa expandir para entender (por exemplo, com gcc -E), porque os depuradores não podem percorrer as macros, como neste exemplo:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
As funções e constantes embutidas ajudam a evitar muitos desses problemas com macros, mas nem sempre são aplicáveis. Quando as macros são usadas deliberadamente para especificar o comportamento polimórfico, o polimorfismo não intencional pode ser difícil de evitar. C ++ tem uma série de recursos, como modelos para ajudar a criar construções polimórficas complexas de uma maneira segura de tipos, sem o uso de macros; consulte The C ++ Programming Language de Stroustrup para obter detalhes.
x++*x++
não se pode dizer que a expressão aumenta x
duas vezes; na verdade, ele invoca um comportamento indefinido , o que significa que o compilador está livre para fazer o que quiser - ele pode incrementar x
duas vezes, ou uma vez, ou não aumentar ; ele pode abortar com um erro ou até mesmo fazer demônios voarem pelo seu nariz .
Recursos macro :
Recursos da função :
Os efeitos colaterais são importantes. Aqui está um caso típico:
#define min(a, b) (a < b ? a : b)
min(x++, y)
é expandido para:
(x++ < y ? x++ : y)
x
é incrementado duas vezes na mesma instrução. (e comportamento indefinido)
Escrever macros multilinhas também é uma dor:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Eles exigem um \
no final de cada linha.
As macros não podem "retornar" nada, a menos que você faça uma única expressão:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Não posso fazer isso em uma macro a menos que você use a instrução de expressão do GCC. (EDITAR: Você pode usar um operador vírgula, embora ... esquecido ... Mas ainda pode ser menos legível.)
Ordem de operações: (cortesia de @ouah)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
é expandido para:
(x & 0xFF < 42 ? x & 0xFF : 42)
Mas &
tem precedência menor do que <
. Portanto, 0xFF < 42
é avaliado primeiro.
min(a & 0xFF, 42)
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
enquanto que:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
Comparado com:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
Em caso de dúvida, use funções (ou funções embutidas).
No entanto, as respostas aqui explicam principalmente os problemas com macros, em vez de ter uma visão simples de que macros são ruins porque acidentes bobos são possíveis.
Você pode estar ciente das armadilhas e aprender a evitá-las. Em seguida, use macros apenas quando houver um bom motivo para isso.
Existem certos casos excepcionais em que há vantagens em usar macros, incluindo:
va_args
. __FILE__
, __LINE__
, __func__
). verifique se há condições pré / pós, assert
em caso de falha, ou mesmo declarações estáticas para que o código não compile em uso impróprio (principalmente útil para compilações de depuração).struct
membros estão presentes antes da conversão func(FOO, "FOO");
, você pode definir uma macro que expande a string para vocêfunc_wrapper(FOO);
inline
funções podem ser uma opção) .Admitidamente, alguns deles dependem de extensões de compilador que não são o padrão C. Significa que você pode acabar com código menos portável, ou ter que ifdef
usá - los, então eles só serão aproveitados quando o compilador suportar.
Observando isso, já que é uma das causas mais comuns de erros em macros (passagem, x++
por exemplo, onde uma macro pode ser incrementada várias vezes) .
é possível escrever macros que evitam efeitos colaterais com múltiplas instanciações de argumentos.
Se você gosta de square
macros que funcionem com vários tipos e suporte C11, você pode fazer isso ...
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
Esta é uma extensão do compilador suportada por GCC, Clang, EKOPath e Intel C ++ (mas não MSVC) ;
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
Portanto, a desvantagem das macros é que você precisa saber como usá-las para começar, e que elas não têm suporte tão amplo.
Um benefício é, neste caso, você pode usar a mesma square
função para muitos tipos diferentes.
Nenhuma verificação de tipo de parâmetros e código é repetida, o que pode levar ao inchaço do código. A sintaxe da macro também pode levar a qualquer número de casos extremos estranhos onde ponto-e-vírgula ou ordem de precedência podem atrapalhar. Aqui está um link que demonstra algum mal macro
uma desvantagem das macros é que os depuradores leem o código-fonte, que não tem macros expandidas, portanto, executar um depurador em uma macro não é necessariamente útil. Desnecessário dizer que você não pode definir um ponto de interrupção dentro de uma macro como faz com as funções.
As funções fazem a verificação de tipo. Isso oferece uma camada extra de segurança.
Adicionando a esta resposta ..
As macros são substituídas diretamente no programa pelo pré-processador (já que são basicamente diretivas do pré-processador). Portanto, eles inevitavelmente usam mais espaço de memória do que uma função respectiva. Por outro lado, uma função requer mais tempo para ser chamada e retornar resultados, e essa sobrecarga pode ser evitada usando macros.
Além disso, as macros têm algumas ferramentas especiais que podem ajudar na portabilidade do programa em diferentes plataformas.
As macros não precisam ser atribuídas a um tipo de dados para seus argumentos, em contraste com as funções.
No geral, eles são uma ferramenta útil na programação. E tanto macroinstruções quanto funções podem ser usadas dependendo das circunstâncias.
Não notei, nas respostas acima, uma vantagem das funções sobre as macros que considero muito importante:
Funções podem ser passadas como argumentos, macros não.
Exemplo concreto: Você deseja escrever uma versão alternativa da função padrão 'strpbrk' que aceitará, em vez de uma lista explícita de caracteres para pesquisar dentro de outra string, uma função (ponteiro para a) que retornará 0 até que um caractere seja descobriu que passa em algum teste (definido pelo usuário). Uma razão pela qual você pode querer fazer isso é para poder explorar outras funções de biblioteca padrão: em vez de fornecer uma string explícita cheia de pontuação, você poderia passar 'ispunct' de ctype.h em vez disso, etc. Se 'ispunct' foi implementado apenas como uma macro, isso não funcionaria.
Existem muitos outros exemplos. Por exemplo, se sua comparação for realizada por macro ao invés de função, você não pode passá-la para 'qsort' de stdlib.h.
Uma situação análoga em Python é 'imprimir' na versão 2 vs. versão 3 (declaração não passável vs. função passável).
Se você passar a função como um argumento para a macro, ela será avaliada todas as vezes. Por exemplo, se você chamar uma das macros mais populares:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
Curtiu isso
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTime será avaliado 5 vezes, o que pode diminuir significativamente o desempenho