Funkcje wbudowane a makra preprocesora

Odpowiedzi:

127

Makra preprocesora to po prostu wzorce podstawiania stosowane w kodzie. Mogą być używane prawie w każdym miejscu w kodzie, ponieważ są zastępowane ich rozszerzeniami przed rozpoczęciem jakiejkolwiek kompilacji.

Funkcje wbudowane to rzeczywiste funkcje, których treść jest bezpośrednio umieszczana w witrynie wywołania. Można ich używać tylko wtedy, gdy wywołanie funkcji jest odpowiednie.

Teraz, jeśli chodzi o używanie makr i funkcji wbudowanych w kontekście podobnym do funkcji, pamiętaj, że:

  • Makra nie są bezpieczne pod względem typów i można je rozszerzać niezależnie od tego, czy są poprawne składniowo - faza kompilacji zgłosi błędy wynikające z problemów z rozszerzaniem makr.
  • Makra mogą być używane w kontekście, w którym się nie spodziewasz, powodując problemy
  • Makra są bardziej elastyczne, ponieważ mogą rozszerzać inne makra - podczas gdy funkcje wbudowane niekoniecznie to robią.
  • Makra mogą powodować skutki uboczne z powodu ich rozwinięcia, ponieważ wyrażenia wejściowe są kopiowane wszędzie tam, gdzie pojawiają się we wzorcu.
  • Funkcja wbudowana nie zawsze jest wbudowana - niektóre kompilatory robią to tylko w kompilacjach wydania lub gdy są specjalnie do tego skonfigurowane. W niektórych przypadkach może nie być możliwe wstawianie inliningu.
  • Funkcje wbudowane mogą zapewniać zakres dla zmiennych (szczególnie statycznych), makra preprocesora mogą to robić tylko w blokach kodu {...}, a zmienne statyczne nie będą zachowywać się dokładnie w ten sam sposób.
LBushkin
źródło
39
Funkcja wbudowana nie zawsze jest wbudowana: ponieważ kompilator nie będzie wbudowany, jeśli spowoduje to wygenerowanie wolniejszego kodu itp. Kompilator przeprowadza wiele analiz, których Inżynier nie może i robi to, co należy.
Martin York,
14
Uważam, że funkcje rekurencyjne są kolejnym przykładem, w którym większość kompilatorów ignoruje inlining.
LBushkin
Czy w tym przypadku są jakieś istotne różnice w języku C w porównaniu z C ++?
rzetterberg
7
Jedną z kwestii, o której nie wspomniano, jest to, że na tworzenie treści w liniach mogą wpływać flagi kompilacji. Na przykład, kiedy budujesz dla maksymalnej szybkości (jak GCC -O2 / -O3), kompilator wybierze wbudowane wiele funkcji, ale kiedy budujesz dla minimalnego rozmiaru (-Os), jeśli zwykle wbudowane funkcje będą wywoływane tylko raz (lub bardzo małe funkcje ). W przypadku makr nie ma takiego wyboru.
dbrank0
Makra nie mogą być objęte specyfikatorem dostępu (takim jak prywatny lub chroniony), podczas gdy możliwe są funkcje wbudowane.
Hit:
78

Po pierwsze, makra preprocesora są po prostu „kopiuj wklej” w kodzie przed kompilacją. Nie ma więc sprawdzania typu i mogą pojawić się pewne efekty uboczne

Na przykład, jeśli chcesz porównać 2 wartości:

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

Efekty uboczne pojawiają się, jeśli używasz max(a++,b++)na przykład ( alubb zostaną zwiększone dwukrotnie). Zamiast tego użyj (na przykład)

inline int max( int a, int b) { return ((a<b)?b:a); }
ThibThib
źródło
3
Chcę tylko dodać do swojego przykładu, że poza efektem ubocznym makro może również wprowadzić dodatkowe obciążenie pracą, max(fibonacci(100), factorial(10000))weź pod uwagę, że większe zostanie obliczone dwukrotnie :(
watashiSHUN
Wszyscy mówią o sprawdzaniu typów, ale tylko Ty podałeś przykład ze świata rzeczywistego, dlatego głosuję za tą odpowiedzią.
Ivanzinho
16

Funkcje Inline są rozszerzane przez kompilator, gdzie makra są rozszerzane przez Preprocessor, który jest zwykłym podstawieniem tekstowym.

  • Nie ma sprawdzania typów podczas wywoływania makra, podczas gdy sprawdzanie typów jest wykonywane podczas wywołania funkcji.

  • Niepożądane wyniki i nieefektywność mogą wystąpić podczas ekspansji makr z powodu ponownej oceny argumentów i kolejności operacji. Na przykład

    #define MAX(a,b) ((a)>(b) ? (a) : (b))
    int i = 5, j = MAX(i++, 0);

    spowoduje

    int i = 5, j = ((i++)>(0) ? (i++) : (0));
  • Argumenty makr nie są oceniane przed rozwinięciem makra

    #define MUL(a, b) a*b
    int main()
    {
      // The macro is expended as 2 + 3 * 3 + 5, not as 5*8
      printf("%d", MUL(2+3, 3+5));
     return 0;
    }
    // Output: 16`
  • Słowa kluczowego return nie można używać w makrach do zwracania wartości, jak w przypadku funkcji.

  • Funkcje wbudowane mogą być przeciążone

  • Tokeny przekazane do makr można łączyć za pomocą operatora ## zwanego operatorem Token-Pasting.

  • Makra są zwykle używane do ponownego wykorzystania kodu, gdzie funkcje wbudowane są używane do wyeliminowania narzutu czasu (nadmiaru czasu) podczas wywołania funkcji (unikanie skoku do podprogramu).

DataCruncher
źródło
13

Kluczową różnicą jest sprawdzanie typu. Kompilator sprawdzi, czy to, co przekazujesz jako wartości wejściowe, jest typu, który można przekazać do funkcji. Nie jest to prawdą w przypadku makr preprocesora - są one rozwijane przed jakimkolwiek sprawdzaniem typu i mogą powodować poważne i trudne do wykrycia błędy.

Oto kilka innych, mniej oczywistych punktów.

ostry
źródło
11

Aby dodać kolejną różnicę do już podanych: nie możesz przejść przez a #definew debugerze, ale możesz przejść przez funkcję wbudowaną.

RichieHindle
źródło
8

Makra ignorują przestrzenie nazw. I to czyni ich złymi.

Kirill V. Lyadvinsky
źródło
3

Funkcje wbudowane są podobne do makr (ponieważ kod funkcji jest rozwijany w momencie wywołania w czasie kompilacji), funkcje wbudowane są analizowane przez kompilator, podczas gdy makra są rozszerzane przez preprocesor. W rezultacie istnieje kilka ważnych różnic:

  • Funkcje wbudowane są zgodne ze wszystkimi protokołami bezpieczeństwa typu narzucanymi w normalnych funkcjach.
  • Funkcje wbudowane są określane przy użyciu tej samej składni, co każda inna funkcja, z wyjątkiem tego, że zawierają słowo kluczowe wbudowane w deklaracji funkcji.
  • Wyrażenia przekazane jako argumenty do funkcji wbudowanych są oceniane tylko raz.
  • W niektórych przypadkach wyrażenia przekazane jako argumenty do makr mogą być oceniane więcej niż raz. http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx

  • makra są rozwijane przed kompilacją, nie można ich używać do debugowania, ale można używać funkcji wbudowanych.

- dobry artykuł : http://www.codeguru.com/forum/showpost.php?p=1093923&postcount=1

;

Ahmad
źródło
2

Funkcja inline zachowa semantykę wartości, podczas gdy makro preprocesora po prostu kopiuje składnię. Możesz uzyskać bardzo subtelne błędy z makrem preprocesora, jeśli użyjesz argumentu wiele razy - na przykład, jeśli argument zawiera mutację typu „i ++”, wykonanie tego dwukrotnego wykonania jest sporym zaskoczeniem. Funkcja inline nie będzie miała tego problemu.

Michael Donohue
źródło
1

Funkcja wbudowana zachowuje się składniowo tak jak normalna funkcja, zapewniając bezpieczeństwo typów i zakres dla lokalnych zmiennych funkcji oraz dostęp do elementów składowych klasy, jeśli jest to metoda. Również podczas wywoływania metod wbudowanych należy przestrzegać ograniczeń prywatnych / chronionych.

heeen
źródło
1

Aby poznać różnicę między makrem a funkcją inline , najpierw powinniśmy wiedzieć, czym dokładnie są i kiedy powinniśmy ich używać.

FUNKCJE :

int Square(int x){
return(x*X);
}
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • Z wywołaniami funkcji wiąże się narzut, ponieważ po zakończeniu wykonywania funkcji musi wiedzieć, dokąd ma zwrócić, a także musi przechowywać wartość w pamięci stosu.

  • W przypadku małych aplikacji nie będzie to problem, ale weźmy przykład aplikacji finansowych, w których tysiące transakcji odbywają się co sekundę, nie możemy iść z wywołaniami funkcji.

MAKRA:

# define Square(x) x*x;
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • Makra działają na etapie preprocesingu tzn. Na tym etapie instrukcje zapisane słowem kluczowym # zostaną zastąpione treścią tj

int wynik = Kwadrat (x * x)

Ale makra mają związane z tym błędy.

#define Square(x) x*x
int main() {
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

Tutaj wynik wynosi 11, a nie 36 .

FUNKCJE INLINE :

inline int Square(int x) {
    return x * x;
}

int main() {
    using namespace std;
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

Wyjście 36

Słowo kluczowe Inline żąda od kompilatora zastąpienia wywołania funkcji treścią funkcji, w tym przypadku dane wyjściowe są poprawne, ponieważ najpierw ocenia wyrażenie, a następnie są przekazywane. Zmniejsza obciążenie wywołania funkcji, ponieważ nie ma potrzeby przechowywania adresu zwrotnego i stosu pamięć nie jest wymagana dla argumentów funkcji.

Porównanie makr i funkcji wbudowanych:

  1. Makra działają poprzez podstawianie, podczas gdy w funkcjach wbudowanych wywołanie funkcji jest zastępowane treścią.
  2. Makra są podatne na błędy ze względu na podstawianie, podczas gdy funkcje wbudowane są bezpieczne w użyciu.
  3. Makra nie mają adresu, podczas gdy funkcje wbudowane mają adres.
  4. Makra są trudne w użyciu z wieloma wierszami kodu, podczas gdy funkcje wbudowane nie.
  5. W C ++ makra nie mogą być używane z funkcjami składowymi, podczas gdy funkcja wbudowana może być.

WNIOSEK:

Funkcje wbudowane są czasami bardziej przydatne niż makra, ponieważ poprawiają wydajność i są bezpieczne w użyciu, a także zmniejszają narzut wywołań funkcji. To tylko żądanie do kompilatora, niektóre funkcje nie będą wbudowane, jak:

  • duże funkcje
  • funkcje mające zbyt wiele argumentów warunkowych
  • kod rekurencyjny i kod z pętlami itp.

co jest dobrą rzeczą, ponieważ wtedy kompilator uważa, że ​​najlepiej zrobić coś w inny sposób.

Arkusz
źródło
Uwaga: makro można naprawić, aby obliczać tę samą liczbę za pomocą nawiasów. Jednak nadal jest podatny na błędy, ponieważ musisz pomyśleć o absolutnej głupiej substytucji i wszystkich przypadkach podczas implementacji.
Mike
0

W GCC (nie jestem pewien co do innych) deklarowanie funkcji w linii jest tylko wskazówką dla kompilatora. Nadal do kompilatora na koniec dnia należy decyzja, czy zawiera on treść funkcji przy każdym wywołaniu.

Różnica między funkcjami wbudowanymi a makrami preprocesora jest stosunkowo duża. Makra preprocesora pod koniec dnia zastępują tylko tekst. Zrezygnowałeś w dużej mierze z możliwości kompilatora do sprawdzania sprawdzania typów argumentów i zwracanego typu. Ocena argumentów jest zupełnie inna (jeśli wyrażenia, które przekazujesz do funkcji, mają skutki uboczne, debugowanie będzie bardzo przyjemne). Istnieją subtelne różnice w miejscach, w których można używać funkcji i makr. Na przykład gdybym miał:

#define MACRO_FUNC(X) ...

Gdzie MACRO_FUNC oczywiście definiuje treść funkcji. Należy zachować szczególną ostrożność, aby działała poprawnie we wszystkich przypadkach, w których można użyć funkcji, na przykład źle napisana funkcja MACRO_FUNC spowodowałaby błąd w

if(MACRO_FUNC(y)) {
 ...body
}

Można tam bez problemu użyć normalnej funkcji.

Falaina
źródło
0

Z punktu widzenia kodowania funkcja inline jest podobna do funkcji. Zatem różnice między funkcją wbudowaną a makrem są takie same, jak różnice między funkcją a makrem.

Z punktu widzenia kompilacji funkcja inline jest podobna do makra. Jest wstrzykiwany bezpośrednio do kodu, a nie wywoływany.

Ogólnie rzecz biorąc, powinieneś traktować funkcje wbudowane jako zwykłe funkcje z pewnymi pomniejszymi optymalizacjami. I podobnie jak w przypadku większości optymalizacji, kompilator decyduje, czy rzeczywiście chce je zastosować. Często kompilator z radością ignoruje wszelkie próby wbudowania funkcji przez programistę z różnych powodów.

Brian
źródło
0

Funkcje wbudowane będą zachowywać się jak wywołanie funkcji, jeśli istnieje w nich jakakolwiek instrukcja iteracyjna lub rekurencyjna, aby zapobiec powtarzaniu się instrukcji. Bardzo pomocne jest zachowanie ogólnej pamięci programu.

Arashdeep singh
źródło
-1
#include<iostream>
using namespace std;
#define NUMBER 10 //macros are preprocessed while functions are not.
int number()
{ 
    return 10;
}
/*In macros, no type checking(incompatible operand, etc.) is done and thus use of micros can lead to errors/side-effects in some cases. 
However, this is not the case with functions.
Also, macros do not check for compilation error (if any). Consider:- */
#define CUBE(b) b*b*b
int cube(int a)
{
 return a*a*a;
}
int main()
{
 cout<<NUMBER<<endl<<number()<<endl;
 cout<<CUBE(1+3); //Unexpected output 10
 cout<<endl<<cube(1+3);// As expected 64
 return 0;
}

Makra są zwykle szybsze niż funkcje, ponieważ nie obejmują rzeczywistego obciążenia wywołania funkcji.

Niektóre wady makr: nie ma sprawdzania typu, trudne do debugowania, ponieważ powodują prostą zamianę, makra nie mają przestrzeni nazw, więc makro w jednej sekcji kodu może wpływać na inną sekcję. Makra mogą powodować efekty uboczne, jak pokazano w powyższym przykładzie CUBE ().

Makra to zwykle jedna linijka. Mogą jednak składać się z więcej niż jednej linii, więc w funkcjach nie ma takich ograniczeń.

rahul_107
źródło
O ile więcej radości daje Ci #define TWO_N(n) 2 << nwtedy cout << CUBE(TWO_N(3 + 1)) << endl;? (Lepiej jest kończyć wiersze wyjścia, endlniż nim zaczynać.)
Jonathan Leffler