Inline-Funktionen im Vergleich zu Präprozessor-Makros


Antworten:


127

Präprozessor-Makros sind nur Substitutionsmuster, die auf Ihren Code angewendet werden. Sie können fast überall in Ihrem Code verwendet werden, da sie vor Beginn der Kompilierung durch ihre Erweiterungen ersetzt werden.

Inline-Funktionen sind tatsächliche Funktionen, deren Körper direkt in ihre Anrufstelle injiziert wird. Sie können nur verwendet werden, wenn ein Funktionsaufruf angemessen ist.

Wenn Sie nun Makros oder Inline-Funktionen in einem funktionsähnlichen Kontext verwenden möchten, beachten Sie Folgendes:

  • Makros sind nicht typsicher und können erweitert werden, unabhängig davon, ob sie syntaktisch korrekt sind. In der Kompilierungsphase werden Fehler gemeldet, die auf Makroerweiterungsprobleme zurückzuführen sind.
  • Makros können in Kontexten verwendet werden, in denen Sie keine Erwartungen haben, was zu Problemen führt
  • Makros sind flexibler, da sie andere Makros erweitern können - während Inline-Funktionen dies nicht unbedingt tun.
  • Makros können aufgrund ihrer Erweiterung zu Nebenwirkungen führen, da die Eingabeausdrücke überall dort kopiert werden, wo sie im Muster erscheinen.
  • Es ist nicht immer garantiert, dass Inline-Funktionen inline sind - einige Compiler tun dies nur in Release-Builds oder wenn sie speziell dafür konfiguriert sind. In einigen Fällen ist Inlining möglicherweise nicht möglich.
  • Inline-Funktionen bieten Platz für Variablen (insbesondere statische), Präprozessormakros können dies nur in Codeblöcken {...} tun, und statische Variablen verhalten sich nicht genau gleich.

39
Es wird nicht immer garantiert, dass Inline-Funktionen inline sind: Da der Compiler nicht inline ist, wenn dies langsamerer Code usw. generiert. Der Compiler führt viele Analysen durch, die der Engineer nicht durchführen kann und tut.
Martin York

14
Ich glaube, dass rekursive Funktionen ein weiteres Beispiel sind, bei dem die meisten Compiler Inlining ignorieren.
LBushkin

Gibt es in diesem Fall wichtige Unterschiede zwischen C und C ++?
Rzetterberg

7
Ein nicht erwähnter Punkt ist, dass Inlining durch Kompilierungsflags beeinflusst werden kann. Wenn Sie beispielsweise für maximale Geschwindigkeit erstellen (wie GCC -O2 / -O3), wählt der Compiler, viele Funktionen zu integrieren, aber wenn Sie für minimale Größe (-Os) erstellen, werden Inline-Funktionen normalerweise nur einmal aufgerufen (oder sehr kleine Funktionen) ). Bei Makros gibt es keine solche Wahl.
dbrank0

Makros können nicht mit Zugriffsspezifizierern (wie privat oder geschützt) abgedeckt werden, solange Inline-Funktionen möglich sind.
Hit ist

78

Erstens sind die Präprozessor-Makros nur "Kopieren Einfügen" im Code vor der Kompilierung. Es findet also keine Typprüfung statt und es können einige Nebenwirkungen auftreten

Zum Beispiel, wenn Sie 2 Werte vergleichen möchten:

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

Die Nebenwirkungen treten beispielsweise bei Verwendung max(a++,b++)auf ( aoder bwerden zweimal erhöht). Verwenden Sie stattdessen (zum Beispiel)

inline int max( int a, int b) { return ((a<b)?b:a); }

3
Ich möchte Ihrem Beispiel nur hinzufügen, dass das Makro neben den Nebenwirkungen auch eine zusätzliche Arbeitsbelastung verursachen kann. max(fibonacci(100), factorial(10000))
Beachten Sie

Alle sprechen über Typprüfung, aber nur Sie haben ein Beispiel aus der Praxis geliefert. Deshalb stimme ich dieser Antwort zu.
Ivanzinho

16

Die Inline-Funktion wird vom Compiler erweitert, während die Makros vom Präprozessor erweitert werden. Dies ist lediglich eine Textsubstitution

  • Während des Makroaufrufs findet keine Typprüfung statt, während die Typprüfung während des Funktionsaufrufs erfolgt.

  • Unerwünschte Ergebnisse und Ineffizienzen können während der Makroerweiterung aufgrund einer Neubewertung von Argumenten und der Reihenfolge der Operationen auftreten. Beispielsweise

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

    würde dazu führen

    int i = 5, j = ((i++)>(0) ? (i++) : (0));
  • Die Makroargumente werden vor der Makroerweiterung nicht ausgewertet

    #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`
  • Das Schlüsselwort return kann in Makros nicht verwendet werden, um Werte zurückzugeben, wie im Fall von Funktionen.

  • Inline-Funktionen können überlastet werden

  • Die an Makros übergebenen Token können mit dem Operator ## verkettet werden, der als Token-Pasting-Operator bezeichnet wird.

  • Makros werden im Allgemeinen für die Wiederverwendung von Code verwendet, wobei Inline-Funktionen verwendet werden, um den Zeitaufwand (überschüssige Zeit) während des Funktionsaufrufs zu vermeiden (um einen Sprung zu einer Unterroutine zu vermeiden).


13

Der Hauptunterschied ist die Typprüfung. Der Compiler prüft, ob das, was Sie als Eingabewerte übergeben, vom Typ ist, der an die Funktion übergeben werden kann. Dies gilt nicht für Präprozessor-Makros. Sie werden vor jeder Typprüfung erweitert und können schwerwiegende und schwer zu erkennende Fehler verursachen.

Hier sind einige andere weniger offensichtliche Punkte umrissen.


11

Um einen weiteren Unterschied zu den bereits angegebenen hinzuzufügen: Sie können #defineim Debugger nicht durch eine , sondern durch eine Inline-Funktion gehen.



3

Inline-Funktionen ähneln Makros (da der Funktionscode zum Zeitpunkt des Aufrufs zur Kompilierungszeit erweitert wird), Inline-Funktionen werden vom Compiler analysiert, während Makros vom Präprozessor erweitert werden. Infolgedessen gibt es mehrere wichtige Unterschiede:

  • Inline-Funktionen folgen allen Protokollen der Typensicherheit, die für normale Funktionen gelten.
  • Inline-Funktionen werden mit derselben Syntax wie jede andere Funktion angegeben, außer dass sie das Inline-Schlüsselwort in der Funktionsdeklaration enthalten.
  • Ausdrücke, die als Argumente an Inline-Funktionen übergeben werden, werden einmal ausgewertet.
  • In einigen Fällen können Ausdrücke, die als Argumente an Makros übergeben werden, mehrmals ausgewertet werden. http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx

  • Makros werden vor der Kompilierung erweitert. Sie können sie nicht zum Debuggen verwenden, aber Sie können Inline-Funktionen verwenden.

- guter Artikel : http://www.codeguru.com/forum/showpost.php?p=1093923&postcount=1

;;


2

Eine Inline-Funktion behält die Wertesemantik bei, während ein Präprozessor-Makro nur die Syntax kopiert. Wenn Sie das Argument mehrmals verwenden, können mit einem Präprozessor-Makro sehr subtile Fehler auftreten. Wenn das Argument beispielsweise eine Mutation wie "i ++" enthält, die zweimal ausgeführt wird, ist dies eine ziemliche Überraschung. Eine Inline-Funktion hat dieses Problem nicht.


1

Eine Inline-Funktion verhält sich syntaktisch wie eine normale Funktion und bietet Typensicherheit sowie einen Bereich für lokale Funktionsvariablen und den Zugriff auf Klassenmitglieder, wenn es sich um eine Methode handelt. Auch beim Aufrufen von Inline-Methoden müssen Sie private / geschützte Einschränkungen einhalten.


1

Um den Unterschied zwischen Makro- und Inline-Funktion zu erkennen , sollten wir zunächst wissen, was genau sie sind und wann wir sie verwenden sollten.

FUNKTIONEN :

int Square(int x){
return(x*X);
}
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • Mit Funktionsaufrufen ist Overhead verbunden, da die Funktion nach Abschluss der Ausführung wissen muss, wohin sie zurückkehren muss, und den Wert auch im Stapelspeicher speichern muss.

  • Für kleine Anwendungen ist dies kein Problem, aber nehmen wir ein Beispiel für Finanzanwendungen, bei denen jede Sekunde Tausende von Transaktionen ausgeführt werden. Wir können keine Funktionsaufrufe ausführen.

MAKROS:

# define Square(x) x*x;
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • Makros arbeiten in der Vorverarbeitungsphase, dh in dieser Phase werden die mit dem Schlüsselwort # geschriebenen Anweisungen durch den Inhalt ersetzt, d. H.

int result = Quadrat (x * x)

Mit Makros sind jedoch Fehler verbunden.

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

Hier ist die Ausgabe 11 nicht 36 .

INLINE-FUNKTIONEN :

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;
}

Ausgabe 36

Das Inline-Schlüsselwort fordert den Compiler auf, den Funktionsaufruf durch den Hauptteil der Funktion zu ersetzen. Hier ist die Ausgabe korrekt, da zuerst der Ausdruck ausgewertet und dann übergeben wird. Dadurch wird der Aufwand für Funktionsaufrufe verringert, da die Rücksprungadresse und der Stapel nicht gespeichert werden müssen Für Funktionsargumente ist kein Speicher erforderlich.

Vergleich zwischen Makros und Inline-Funktionen:

  1. Makros funktionieren durch Ersetzen, während in Inline-Funktionen der Funktionsaufruf durch den Body ersetzt wird.
  2. Makros sind aufgrund von Ersetzungen fehleranfällig, während Inline-Funktionen sicher verwendet werden können.
  3. Makros haben keine Adresse, während Inline-Funktionen eine Adresse haben.
  4. Makros sind mit mehreren Codezeilen schwierig zu verwenden, Inline-Funktionen hingegen nicht.
  5. In C ++ können Makros nicht mit Elementfunktionen verwendet werden, wohingegen Inline-Funktionen verwendet werden können.

FAZIT:

Inline-Funktionen sind manchmal nützlicher als Makros, da sie die Leistung verbessern, sicher zu verwenden sind und den Aufwand für Funktionsaufrufe verringern. Es ist nur eine Anfrage an den Compiler, bestimmte Funktionen werden nicht wie folgt eingefügt:

  • große Funktionen
  • Funktionen mit zu vielen bedingten Argumenten
  • rekursiver Code und Code mit Schleifen usw.

Das ist eine gute Sache, denn das ist immer dann, wenn der Compiler denkt, dass es am besten ist, die Dinge anders zu machen.


Nur als Bemerkung: Das Makro kann so fixiert werden, dass es mit Klammern auf dieselbe Zahl ausgewertet wird. Es ist jedoch immer noch fehleranfällig, da Sie während der Implementierung über die absolut dumme Substitution und alle Fälle nachdenken müssen.
Mike

0

In GCC (bei anderen bin ich mir nicht sicher) ist das Deklarieren einer Funktion inline nur ein Hinweis für den Compiler. Am Ende des Tages muss der Compiler noch entscheiden, ob er den Funktionskörper enthält, wenn er aufgerufen wird.

Der Unterschied zwischen Inline-Funktionen und Präprozessormakros ist relativ groß. Präprozessor-Makros sind letztendlich nur Textersetzung. Sie geben dem Compiler viel von der Möglichkeit auf, die Typprüfung der Argumente und den Rückgabetyp zu überprüfen. Die Bewertung der Argumente ist sehr unterschiedlich (wenn die Ausdrücke, die Sie an die Funktionen übergeben, Nebenwirkungen haben, macht das Debuggen sehr viel Spaß). Es gibt subtile Unterschiede, wo Funktionen und Makros verwendet werden können. Zum Beispiel, wenn ich hätte:

#define MACRO_FUNC(X) ...

Wobei MACRO_FUNC offensichtlich den Hauptteil der Funktion definiert. Besondere Vorsicht ist geboten, damit es in allen Fällen korrekt ausgeführt wird. Eine Funktion kann verwendet werden. Beispielsweise würde ein schlecht geschriebener MACRO_FUNC einen Fehler in verursachen

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

Eine normale Funktion könnte dort problemlos verwendet werden.


0

Aus Sicht der Codierung ist eine Inline-Funktion wie eine Funktion. Somit sind die Unterschiede zwischen einer Inline-Funktion und einem Makro dieselben wie die Unterschiede zwischen einer Funktion und einem Makro.

Aus der Sicht des Kompilierens ähnelt eine Inline-Funktion einem Makro. Es wird direkt in den Code eingefügt und nicht aufgerufen.

Im Allgemeinen sollten Sie Inline-Funktionen als reguläre Funktionen mit geringfügigen Optimierungen betrachten. Wie bei den meisten Optimierungen muss der Compiler entscheiden, ob er sie tatsächlich anwenden möchte. Oft ignoriert der Compiler aus verschiedenen Gründen alle Versuche des Programmierers, eine Funktion zu integrieren.


0

Inline-Funktionen verhalten sich wie ein Funktionsaufruf, wenn eine iterative oder rekursive Anweisung darin vorhanden ist, um die wiederholte Ausführung von Anweisungen zu verhindern. Es ist sehr hilfreich, den Gesamtspeicher Ihres Programms zu speichern.


-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;
}

Makros sind normalerweise schneller als Funktionen, da sie keinen tatsächlichen Funktionsaufruf-Overhead beinhalten.

Einige Nachteile von Makros: Es gibt keine Typprüfung. Schwierig zu debuggen, da sie einen einfachen Ersatz verursachen. Makro hat keinen Namespace, daher kann ein Makro in einem Codeabschnitt einen anderen Abschnitt beeinflussen. Makros können Nebenwirkungen verursachen, wie im obigen Beispiel CUBE () gezeigt.

Makros sind normalerweise einzeilig. Sie können jedoch aus mehr als einer Zeile bestehen. In Funktionen gibt es keine derartigen Einschränkungen.


Wie viel mehr Spaß bekommst du von #define TWO_N(n) 2 << nund dann cout << CUBE(TWO_N(3 + 1)) << endl;? (Es ist besser, die Ausgabezeilen zu beenden, endlals sie damit zu beginnen.)
Jonathan Leffler
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.