Was ist ein Makro? Unterschied zwischen Makro und Funktion?

16

Ich verstehe das Makrokonzept nicht gut. Was ist ein Makro? Ich verstehe nicht, wie es sich von der Funktion unterscheidet? Sowohl Funktion als auch Makro enthalten einen Codeblock. Wie unterscheiden sich Makro und Funktion?

Edoardo Sorgentone
quelle
15
Beziehen Sie sich auf eine bestimmte Programmiersprache?
mkrieger1
15
Sie müssen genauer sein; Makro bezieht sich auf drei sehr unterschiedliche Konzepte, etwa C-ähnliche Text- / Präprozessor-Makros, Lisp-Makros und Anwendungsmakros.
chrylis -on strike-

Antworten:

7

Hinweis

Ich möchte die folgende Klarstellung hinzufügen, nachdem ich das polarisierende Abstimmungsmuster zu dieser Antwort beobachtet habe.

Die Antwort wurde nicht unter Berücksichtigung der technischen Genauigkeit und der umfassenden Verallgemeinerung verfasst. Es war ein bescheidener Versuch, einem Programmieranfänger den Unterschied zwischen Makros und Funktionen in einfacher Sprache zu erklären, ohne zu versuchen, vollständig oder genau zu sein (in der Tat ist es alles andere als genau). Die Programmiersprache C hatte ich bei der Ausarbeitung der Antwort im Hinterkopf, entschuldigte mich jedoch dafür, dass ich sie nicht klar erwähnte und (potenzielle) Verwirrung stiftete.

Ich schätze die Antwort von Jörg W. Mittag sehr . Es war aufschlussreich, es zu lesen (wie wenig ich weiß) und ich habe es bald nach der Veröffentlichung bewertet. Ich habe gerade mit Software Engineering Stack Exchange angefangen und die bisherigen Erfahrungen und Diskussionen waren wirklich aufschlussreich.

Ich werde diese Antwort hier belassen, da sie für andere Softwareentwicklungs-Neulinge hilfreich sein kann, die versuchen, ein Konzept zu verstehen, ohne sich auf technische Genauigkeit einzulassen.


Sowohl Makro als auch Funktion repräsentieren eine in sich geschlossene Codeeinheit. Sie sind beide Werkzeuge, die beim modularen Aufbau eines Programms helfen. Aus der Sicht des Programmierers, der den Quellcode schreibt, sehen sie sich ziemlich ähnlich. Sie unterscheiden sich jedoch darin, wie sie während des Lebenszyklus der Programmausführung behandelt werden.

Ein Makro wird einmal definiert und an vielen Stellen in einem Programm verwendet. Makro wird in der Vorverarbeitungsphase inline erweitert. Somit bleibt es technisch keine separate Entität, sobald der Quellcode kompiliert ist. Die Anweisungen in der Makrodefinition werden wie andere Anweisungen Teil von Programmanweisungen.

Das Motiv beim Schreiben eines Makros besteht darin, dem Programmierer das Schreiben und Verwalten von Quellcode zu erleichtern. Makros sind im Allgemeinen für einfachere Aufgaben erwünscht, bei denen das Schreiben einer vollwertigen Funktion einen Performance-Overhead / Laufzeit-Nachteil bedeuten würde. Beispiele für Situationen, in denen ein Makro der Funktion vorgezogen wird, sind:

  • Verwenden von konstanten Werten (z. B. mathematischen oder wissenschaftlichen Werten) oder programmspezifischen Parametern.

  • Protokollnachrichten drucken oder Zusicherungen verarbeiten.

  • Durchführen einfacher Berechnungen oder Zustandsüberprüfungen.

Bei der Verwendung von Makros ist es einfach, Änderungen / Korrekturen an einer Stelle vorzunehmen, die sofort überall dort verfügbar sind, wo das Makro im Programm verwendet wird. Eine einfache Neukompilierung des Programms ist erforderlich, damit die Änderungen wirksam werden.

Funktionscode hingegen wird als separate Einheit innerhalb des Programms kompiliert und nur bei Bedarf während der Programmausführung in den Speicher geladen. Der Funktionscode behält seine vom Rest des Programms unabhängige Identität. Der geladene Code wird wiederverwendet, wenn die Funktion mehrmals aufgerufen wird. Wenn der Funktionsaufruf im laufenden Programm auftritt, wird die Steuerung vom Laufzeitsubsystem an ihn übergeben, und der Kontext des laufenden Programms (Rückgabeanweisungsadresse) bleibt erhalten.

Beim Aufrufen einer Funktion muss jedoch eine leichte Leistungseinbuße verzeichnet werden (Kontextwechsel, Beibehaltung der Rücksprungadresse der Hauptprogrammanweisungen, Übergabe von Parametern und Behandlung von Rückgabewerten usw.). Daher ist die Verwendung von Funktionen nur für komplexe Codeblöcke erwünscht (gegen Makros, die einfachere Fälle behandeln).

Mit der Erfahrung trifft ein Programmierer eine vernünftige Entscheidung, ob ein Teil des Codes als Makro oder Funktion in die gesamte Programmarchitektur passt.

Nimesh Neema
quelle
9
Was ist mit Funktions-Inlining?
Nathan Cooper
1
In der Sprache C & Co kann ein Makro auch ein Funktionsaufruf sein. Eine Unterscheidung zwischen den beiden macht also wenig Sinn.
Sombrero Chicken
5
Und Makros im C-Stil sind alles andere als eigenständig - sie enthalten keinen lexikalischen Geltungsbereich und haben uneingeschränkten Zugriff auf lokale Namen im Geltungsbereich an der Substitutionsstelle.
Nutzlos
@ Sombrero Chicken: Könntest du vielleicht ein Beispiel dafür zeigen? Sie können Makros haben, die die Funktion CONTAIN aufrufen, aber (AFAIK) das Makro ist kein Funktionsaufruf.
Jamesqf
3
Es gibt viele Dinge an dieser Antwort, die irreführend sind. Die Beispiele , die Sie erwähnen , sind absolut nicht in irgendeiner Weise Situationen , in denen Makros ‚bevorzugt‘ sind - ganz im Gegenteil. Makros sollten für diese Dinge nicht verwendet werden, es sei denn, es gibt einen sehr guten Grund dafür. Die Leistung ist im Allgemeinen kein guter Grund für die Verwendung von Makros. Aus Gründen, die in anderen Kommentaren erwähnt wurden, führt die Rechtfertigung der Verwendung von Makros auf diese Weise wahrscheinlich zu fehleranfälligem Code mit allen möglichen unbeabsichtigten Konsequenzen, insbesondere in einer größeren Codebasis.
Ben Cottrell
65

Leider gibt es bei der Programmierung mehrere unterschiedliche Verwendungen des Begriffs "Makro".

In der Lisp-Familie von Sprachen und von ihnen inspirierten Sprachen sowie in vielen modernen funktionalen oder funktional inspirierten Sprachen wie Scala und Haskell sowie einigen imperativen Sprachen wie Boo ist ein Makro ein Stück Code, der zur Kompilierungszeit ausgeführt wird (oder zumindest vor der Laufzeit für Implementierungen ohne Compiler) und kann den Abstract Syntax Tree (oder was auch immer das Äquivalent in der jeweiligen Sprache ist, z. B. in Lisp, es wären die S-Expressions) während der Kompilierung in etwas anderes verwandeln. In vielen Schemaimplementierungen forist ein Makro beispielsweise ein Makro, das sich zu mehreren Aufrufen an den Body erweitert. In statisch typisierten Sprachen sind Makros häufig typsicher, dh sie können keinen Code erzeugen, der nicht gut typisiert ist.

In der C-Sprachfamilie ähneln Makros eher der Textsubstitution. Das bedeutet auch, dass sie Code produzieren können, der nicht gut typisiert oder syntaktisch nicht legal ist.

In Makro-Assemblern beziehen sich "Makros" auf "virtuelle Anweisungen", dh Anweisungen, die die CPU nicht von Haus aus unterstützt, die jedoch nützlich sind. Der Assembler ermöglicht Ihnen daher, diese Anweisungen zu verwenden und sie zu mehreren Anweisungen zu erweitern, die die CPU versteht .

In Anwendungsskripten bezieht sich ein "Makro" auf eine Reihe von Aktionen, die der Benutzer "aufzeichnen" und "wiedergeben" kann.

All dies sind in gewissem Sinne ausführbare Codes, was bedeutet, dass sie in gewissem Sinne als Funktionen angesehen werden können. Bei Lisp-Makros sind ihre Ein- und Ausgabe jedoch Programmfragmente. Im Fall von C sind ihre Eingabe und Ausgabe Token. Die ersten drei haben auch den sehr wichtigen Unterschied, dass sie zur Kompilierzeit ausgeführt werden . Tatsächlich werden C-Präprozessor-Makros, wie der Name schon sagt, tatsächlich ausgeführt, bevor der Code überhaupt den Compiler erreicht .

Jörg W. Mittag
quelle
1
"In statisch typisierten Sprachen sind Makros oft typsicher, dh sie können keinen Code erzeugen, der nicht gut typisiert ist" - wirklich? Auf welche Sprachen beziehen Sie sich? AFAICS, dies ist in der Regel nur in einer typabhängigen Sprache zu gewährleisten. In Haskell sind TH-Makros nur syntaktisch sicher, die Typprüfung wird jedoch anschließend ausgeführt. (Typisch gesehen bieten sie sogar noch weniger Garantien als C ++ - Vorlagen).
Abfahrt
1
Abgesehen von Anwendungsskripten denke ich nicht, dass die drei Beispiele, die Sie nennen, so unterschiedlich sind. Alle führen eine Art Ersetzung im Programm durch, anstatt bei der Ausführung in einen gemeinsam genutzten Code zu springen.
IMSoP
Vielleicht können Sie die Erwähnung von Cs Makros mit dem allgemeinen Konzept von Makrosprachen wie M4 erweitern, da für C im Grunde genommen die Spezifikation eine CPP-
Hilfsmakrosprache
1
Das allgemeine Thema scheint zu sein, dass Makros Code generieren, der als Teil des Quellcodes eines größeren Programms interpretiert wird, anstatt ausgeführt zu werden, wenn das Programm ausgeführt wird.
jpmc26
1
@ jpmc26 Ich würde sagen, das allgemeine Thema ist Substitution, wo man eine kleine Sache macht und es zu einer großen Sache erweitert. Die Makrosprache M4 ist nicht speziell für Code gedacht. Sie können es verwenden, um beliebigen Text zu generieren. Es gibt auch Tastaturmakros, wie diese Antwort erwähnt. Wenn Sie eine oder zwei Tasten drücken, wird die Anzahl der Tastendrücke erhöht. Vim-Makros sind auch so. Speichern Sie eine große Folge von Befehlen im normalen Modus unter einer einzigen Taste, und rufen Sie diese Folge auf, indem Sie den Befehl im normalen Modus ausführen, der sie ausführt.
JoL
2

In der C-Sprachfamilie gibt eine Makrodefinition , ein Präprozessorbefehl, eine parametrisierte Codevorlage an, die beim Makroaufruf ersetzt wird, ohne bei der Definition kompiliert zu werden. Dies bedeutet, dass alle freien Variablen im Kontext des Makroaufrufs gebunden werden sollten. Parameterargumente mit einem Nebeneffekt wie i++könnten durch mehrfache Verwendung des Parameters wiederholt werden. Das Ersetzen von Argumenten 1 + 2einiger Parameter in Textform xerfolgt vor dem Kompilieren und kann zu unerwartetem Verhalten führen x * 3( 7io 9). Ein Fehler im Makrotext der Makrodefinition wird nur beim Kompilieren beim Makroaufruf angezeigt.

Die Funktionsdefinition gibt Code mit freien Variablen an, die an den Kontext des Funktionskörpers gebunden sind. nicht der Funktionsaufruf .

Das anscheinend negative Makro bietet Zugriff auf den Aufruf, die Zeilennummer und die Quelldatei, das Argument als Zeichenfolge .

Joop Eggen
quelle
2

In etwas abstrakteren Begriffen handelt es sich bei einem Makro um Syntax und bei einer Funktion um Daten. Eine Funktion (in der Zusammenfassung) kapselt einige Transformationen für Daten. Es nimmt seine Argumente als ausgewertete Daten, führt einige Operationen an ihnen durch und gibt ein Ergebnis zurück, das ebenfalls nur Daten sind.

Ein Makro dagegen benötigt eine unbewertete Syntax und arbeitet damit. Bei C-ähnlichen Sprachen erfolgt die Syntax auf Token-Ebene. Für Sprachen mit LISP-ähnlichen Makros wird die Syntax als AST dargestellt. Das Makro muss einen neuen Syntaxblock zurückgeben.

Ashton Wiersdorf
quelle
0

Ein Makro bezieht sich im Allgemeinen auf etwas, das an Ort und Stelle erweitert wird und den Makro- "Aufruf" während der Kompilierung oder Vorverarbeitung durch einzelne Anweisungen in der Zielsprache ersetzt. Zur Laufzeit wird in der Regel nicht angegeben, wo das Makro beginnt und endet.

Dies unterscheidet sich von einer Subroutine , bei der es sich um einen wiederverwendbaren Code handelt, der sich separat im Speicher befindet und an den die Steuerung zur Laufzeit übergeben wird . Die "Funktionen", "Prozeduren" und "Methoden" in den meisten Programmiersprachen fallen in diese Kategorie.

Wie die Antwort von Jörg W. Mittag bespricht, variieren die genauen Details zwischen den Sprachen: In einigen, wie z. B. C, führt ein Makro eine Textsubstitution im Quellcode durch; In einigen Fällen, z. B. bei Lisp, wird eine Zwischenform wie ein abstrakter Syntaxbaum bearbeitet. Es gibt auch einige Grauzonen: Einige Sprachen haben eine Notation für "Inline-Funktionen", die wie eine Funktion definiert, aber wie ein Makro in das kompilierte Programm erweitert werden.

Die meisten Sprachen ermutigen Programmierer, über jedes Unterprogramm einzeln nachzudenken, Typverträge für Ein- und Ausgaben zu definieren und andere Informationen über den aufrufenden Code auszublenden. Das Aufrufen einer Unterroutine, die keine Makros enthält, hat häufig Leistungseinbußen, und Makros können das Programm möglicherweise auf eine Weise manipulieren, die eine Unterroutine nicht kann. Makros können daher als "untergeordnete Ebene" als Subroutinen betrachtet werden, da sie weniger abstrakt arbeiten.

IMSoP
quelle
0

Das Makro wird während der Kompilierung ausgeführt und die Funktion wird zur Laufzeit ausgeführt.

Beispiel:

#include <stdio.h>

#define macro_sum(x,y) (x+y)

int func_sum(x,y) {
    return x+y;
}

int main(void) {
    printf("%d\n", macro_sum(2,3));
    printf("%d\n", func_sum(2,3));
    return 0;
}

Während der Kompilierung wird der Code also geändert in:

#include <stdio.h>

int func_sum(x,y) {
    return x+y;
}

int main(void) {
    printf("%d\n", (2+3));
    printf("%d\n", func_sum(2,3));
    return 0;
}
david72
quelle