Wofür sind C-Makros nützlich?


73

Ich habe ein bisschen C geschrieben und kann es gut genug lesen, um eine allgemeine Vorstellung davon zu bekommen, was es tut, aber jedes Mal, wenn ich auf ein Makro gestoßen bin, hat es mich völlig geworfen. Am Ende muss ich mich daran erinnern, was das Makro ist, und es beim Lesen in meinem Kopf ersetzen. Diejenigen, denen ich begegnet bin, die intuitiv und leicht zu verstehen waren, waren immer wie kleine Minifunktionen, deshalb habe ich mich immer gefragt, warum sie nicht nur Funktionen waren.

Ich kann die Notwendigkeit verstehen, verschiedene Build-Typen für Debug- oder plattformübergreifende Builds im Präprozessor zu definieren, aber die Fähigkeit, beliebige Substitutionen zu definieren, scheint nur nützlich zu sein, um eine bereits schwierige Sprache noch schwieriger zu verstehen.

Warum wurde ein so komplexer Präprozessor für C eingeführt? Und hat jemand ein Beispiel für die Verwendung, das mir verständlich macht, warum es immer noch für andere Zwecke als einfache, wenn auch bedingte Kompilierungen im # debug-Stil verwendet wird?

Bearbeiten:

Nachdem ich einige Antworten gelesen habe, verstehe ich es immer noch nicht. Die häufigste Antwort ist Inline-Code. Wenn das Inline-Schlüsselwort dies nicht tut, hat es entweder einen guten Grund, dies nicht zu tun, oder die Implementierung muss korrigiert werden. Ich verstehe nicht, warum ein ganz anderer Mechanismus benötigt wird, der "diesen Code wirklich inline" bedeutet (abgesehen von dem Code, der geschrieben wurde, bevor es Inline gab). Ich verstehe auch nicht die Idee, die erwähnt wurde, dass "wenn es zu dumm ist, um in eine Funktion gesetzt zu werden". Sicherlich wird jeder Code, der eine Eingabe nimmt und eine Ausgabe erzeugt, am besten in eine Funktion eingefügt. Ich glaube, ich bekomme es vielleicht nicht, weil ich nicht an die Mikrooptimierungen beim Schreiben von C gewöhnt bin, aber der Präprozessor fühlt sich einfach wie eine komplexe Lösung für ein paar einfache Probleme an.


5
Der C-Präprozessor ist nicht komplex, er ist sehr, sehr einfach.
Alex

16
Der C-Präprozessor ist sehr, sehr einfach, aber er macht Ihren Code sehr, sehr komplex. ;)
weberc2


2
Ich bin auf dieses gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros gestoßen, das mir hilft, das Makro in c zu verstehen. Vielleicht hilft das den anderen
pyk

@ Ayxan Bitte nicht inline codezur Hervorhebung verwenden ( stattdessen fett oder kursiv verwenden). Davon abgesehen müssen Sprachnamen wie C überhaupt nicht betont werden.
user694733

Antworten:


55

Am Ende muss ich mich daran erinnern, was das Makro ist, und es beim Lesen in meinem Kopf ersetzen.

Das scheint die Benennung der Makros schlecht zu reflektieren. Ich würde annehmen, dass Sie den Präprozessor nicht emulieren müssten, wenn es ein log_function_entry()Makro wäre.

Diejenigen, denen ich begegnet bin, die intuitiv und leicht zu verstehen waren, waren immer wie kleine Minifunktionen, deshalb habe ich mich immer gefragt, warum sie nicht nur Funktionen waren.

Normalerweise sollten sie es sein, es sei denn, sie müssen mit generischen Parametern arbeiten.

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

funktioniert bei jedem Typ mit einem <Operator.

Mit Makros können Sie nicht nur Funktionen ausführen, sondern auch Operationen mit den Symbolen in der Quelldatei ausführen. Das heißt, Sie können einen neuen Variablennamen erstellen oder auf die Quelldatei und die Zeilennummer verweisen, in der sich das Makro befindet.

In C99 können Sie mit Makros auch verschiedene Funktionen aufrufen, z printf

#define log_message(guard,format,...) \
   if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);

log_message( foo == 7, "x %d", x)

In dem das Format funktioniert wie printf. Wenn der Schutz wahr ist, gibt er die Nachricht zusammen mit der Datei- und Zeilennummer aus, mit der die Nachricht gedruckt wurde. Wenn es sich um einen Funktionsaufruf handeln würde, würde er die Datei und die Zeile, aus der Sie ihn aufgerufen haben, nicht kennen, und die Verwendung von a vaprintfwäre etwas aufwendiger.


3
Vielen Dank. Das ist hilfreich. Ich kann dies als plausible Verwendung für sie sehen. Es bietet ein benutzerfreundliches und übersichtliches Debugging, das nicht einfach durch eine Funktion ersetzt werden kann.
Jack Ryan

19

Dieser Auszug fasst meine Sicht auf die Angelegenheit ziemlich gut zusammen, indem er verschiedene Arten der Verwendung von CMakros und deren Implementierung vergleicht D.

von DigitalMars.com kopiert

Als die CCompilertechnologie erfunden wurde, war sie primitiv. Die Installation eines Textmakro-Präprozessors am Frontend war eine einfache und einfache Möglichkeit, viele leistungsstarke Funktionen hinzuzufügen. Die zunehmende Größe und Komplexität von Programmen hat gezeigt, dass diese Funktionen mit vielen inhärenten Problemen verbunden sind. Dhat keinen Präprozessor; Dbietet aber ein skalierbareres Mittel, um die gleichen Probleme zu lösen.

Makros

Präprozessor-Makros bieten leistungsstarke Funktionen und Flexibilität C. Aber sie haben einen Nachteil:

  • Makros haben kein Konzept des Umfangs; Sie sind vom Definitionspunkt bis zum Ende der Quelle gültig. Sie schneiden einen Schwad über .h-Dateien, verschachtelten Code usw. Bei #includeZehntausenden von Zeilen mit Makrodefinitionen wird es problematisch, versehentliche Makroerweiterungen zu vermeiden.
  • Makros sind dem Debugger unbekannt. Der Versuch, ein Programm mit symbolischen Daten zu debuggen, wird dadurch untergraben, dass der Debugger nur über Makroerweiterungen Bescheid weiß, nicht über die Makros selbst.
  • Makros machen es unmöglich, Quellcode zu tokenisieren, da eine frühere Makroänderung Token willkürlich wiederholen kann.
  • Die rein textuelle Basis von Makros führt zu einer willkürlichen und inkonsistenten Verwendung, wodurch Code mit Makros fehleranfällig wird. (Einige Versuche, dies zu beheben, wurden mit Vorlagen in eingeführt C++.)
  • Makros werden immer noch verwendet, um Defizite in der Ausdrucksfähigkeit der Sprache auszugleichen, z. B. für "Wrapper" um Header-Dateien.

Hier ist eine Aufzählung der gebräuchlichen Verwendungen von Makros und der entsprechenden Funktion in D:

  1. Definieren von Literalkonstanten:

    • Der CPräprozessor-Weg

      #define VALUE 5
      
    • Der DWeg

      const int VALUE = 5;
      
  2. Erstellen einer Liste von Werten oder Flags:

    • Der CPräprozessor-Weg

      int flags:
      #define FLAG_X  0x1
      #define FLAG_Y  0x2
      #define FLAG_Z  0x4
      ...
      flags |= FLAG_X;
      
    • Der DWeg

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  3. Festlegen von Funktionsaufrufkonventionen:

    • Der CPräprozessor-Weg

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • Der DWeg

      Aufrufkonventionen können in Blöcken angegeben werden, sodass sie nicht für jede Funktion geändert werden müssen:

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  4. Einfache generische Programmierung:

    • Der CPräprozessor-Weg

      Auswählen der zu verwendenden Funktion basierend auf der Textersetzung:

      #ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #else
      int getValueA(char *p);
      #define getValue getValueA
      #endif
      
    • Der DWeg

      D ermöglicht Deklarationen von Symbolen, die Aliase anderer Symbole sind:

      version (UNICODE)
      {
          int getValueW(wchar[] p);
          alias getValueW getValue;
      }
      else
      {
          int getValueA(char[] p);
          alias getValueA getValue;
      }
      

Weitere Beispiele finden Sie auf der DigitalMars-Website .


17
Die D-Wege, die Sie geschrieben haben, sind unnötig, würden die meisten denken.
OTZ

Es scheint mir, dass abscheuliche Präprozessor-Direktiven unnötig sind, und ich bin (widerwillig) ein C-Programmierer.
weberc2

1
Erlaubt D, eine Funktion so zu deklarieren, dass der Compiler das Ergebnis durch eine Konstante zur Kompilierungszeit ersetzt, wenn seine Argumente Konstanten zur Kompilierungszeit sind? Natürlich würde es Grenzen für die Dinge geben, die man innerhalb einer solchen Funktion tun könnte, aber es wäre nützlich, foo=bit_reverse(0x12345678);als zu bewerten foo=0x1E6A2C48, aber foo=bit_reverse(bar);einen Funktionsaufruf zu generieren. Es ist möglich, C-Makros mit von gcc stammenden Erweiterungen für solche Zwecke zu verwenden, aber es ist etwas schwierig.
Supercat

1
@supercat Ich denke, es könnte eine konstante Faltung einer Funktion bewirken, wenn die Funktion als deklariert wird pure.
Brad Gilbert

1
@supercat Ich habe die Entwicklung von d in den letzten Jahren nicht wirklich verfolgt .
Brad Gilbert

16

Sie sind eine Programmiersprache (eine einfachere) über C, daher sind sie nützlich für die Metaprogrammierung in der Kompilierungszeit. Mit anderen Worten, Sie können Makrocode schreiben, der C-Code in weniger Zeilen und weniger Zeit generiert Schreiben Sie es direkt in C.

Sie sind auch sehr nützlich, um "funktionale" Ausdrücke zu schreiben, die "polymorph" oder "überladen" sind. zB ein max Makro definiert als:

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

ist nützlich für jeden numerischen Typ; und in C konnte man nicht schreiben:

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

auch wenn Sie wollten, weil Sie Funktionen nicht überladen können.

Und ganz zu schweigen vom bedingten Kompilieren und Einschließen von Dateien (die auch Teil der Makrosprache sind) ...


@ AndrewC Ich dachte, dass es ein bisschen offtopic war ... Dann nicht gelöscht! ;)
fortran

12

Mit Makros kann jemand das Programmverhalten während der Kompilierungszeit ändern. Bedenken Sie:

  • C-Konstanten ermöglichen das Festlegen des Programmverhaltens zur Entwicklungszeit
  • Mit C-Variablen kann das Programmverhalten zur Ausführungszeit geändert werden
  • C-Makros ermöglichen das Ändern des Programmverhaltens zur Kompilierungszeit

Zur Kompilierungszeit bedeutet, dass nicht verwendeter Code nicht einmal in die Binärdatei gelangt und dass der Erstellungsprozess die Werte ändern kann, solange er in den Makro-Präprozessor integriert ist. Beispiel: make ARCH = arm (setzt die Weiterleitungsmakrodefinition als cc -DARCH = arm voraus)

Einfache Beispiele: (Definieren Sie aus glibc limit.h den größten Wert von long.)

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

Überprüft (mit #define __WORDSIZE) zur Kompilierungszeit, ob für 32 oder 64 Bit kompiliert wird. Bei einer Multilib-Toolchain kann die Verwendung der Parameter -m32 und -m64 die Bitgröße automatisch ändern.

(POSIX-Versionsanforderung)

#define _POSIX_C_SOURCE 200809L

Anforderungen während der Kompilierungszeit POSIX 2008-Unterstützung. Die Standardbibliothek unterstützt möglicherweise viele (inkompatible) Standards, bietet jedoch mit dieser Definition die richtigen Funktionsprototypen (Beispiel: getline (), no gets () usw.). Wenn die Bibliothek den Standard nicht unterstützt, wird möglicherweise während der Kompilierungszeit ein Fehler angezeigt, anstatt beispielsweise während der Ausführung abzustürzen.

(fest codierter Pfad)

#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif

Definiert während der Kompilierungszeit ein Hardcode-Verzeichnis. Könnte zum Beispiel mit -DLIBRARY_PATH = / home / user / lib geändert werden. Wenn das ein const char * wäre, wie würden Sie es während der Kompilierung konfigurieren?

(pthread.h, komplexe Definitionen zur Kompilierungszeit)

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

Es können große Textteile deklariert werden, die sonst nicht vereinfacht würden (immer zur Kompilierungszeit). Dies ist mit Funktionen oder Konstanten (zur Kompilierungszeit) nicht möglich.

Um zu vermeiden, dass die Dinge wirklich kompliziert werden und schlechte Codierungsstile vorgeschlagen werden, werde ich kein Beispiel für Code nennen, der in verschiedenen, inkompatiblen Betriebssystemen kompiliert wird. Verwenden Sie dazu Ihr Cross-Build-System, aber es sollte klar sein, dass der Präprozessor dies ohne Hilfe des Build-Systems zulässt, ohne die Kompilierung aufgrund fehlender Schnittstellen zu unterbrechen.

Denken Sie abschließend an die Bedeutung der bedingten Kompilierung auf eingebetteten Systemen, bei denen die Prozessorgeschwindigkeit und der Arbeitsspeicher begrenzt sind und die Systeme sehr heterogen sind.

Wenn Sie fragen, ist es nun möglich, alle Definitionen und Funktionsaufrufe von Makrokonstanten durch geeignete Definitionen zu ersetzen? Die Antwort lautet "Ja", aber die Notwendigkeit, das Programmverhalten während der Kompilierung zu ändern, entfällt. Der Präprozessor wäre weiterhin erforderlich.


11

Denken Sie daran, dass Makros (und der Vorprozessor) aus den frühesten Tagen von C stammen. Früher waren sie die EINZIGE Möglichkeit, Inline-Funktionen auszuführen (da Inline natürlich ein sehr aktuelles Schlüsselwort ist), und sie sind immer noch das Nur so kann man etwas erzwingen, das inline ist.

Makros sind auch die einzige Möglichkeit, Tricks wie das Einfügen der Datei und der Zeile in Zeichenfolgenkonstanten beim Kompilieren auszuführen.

Heutzutage werden viele der Dinge, zu denen Makros früher die einzige Möglichkeit waren, besser durch neuere Mechanismen gehandhabt. Aber sie haben immer noch ihren Platz, von Zeit zu Zeit.


8

Neben Inlining für Effizienz und bedingte Kompilierung können Makros verwendet werden, um die Abstraktionsstufe von C-Code auf niedriger Ebene zu erhöhen. C isoliert Sie nicht wirklich von den Details des Speicher- und Ressourcenmanagements und des genauen Layouts von Daten und unterstützt nur sehr begrenzte Formen des Versteckens von Informationen und andere Mechanismen für die Verwaltung großer Systeme. Mit Makros können Sie nicht mehr nur die Basiskonstrukte in der Sprache C verwenden: Sie können Ihre eigenen Datenstrukturen und Codierungskonstrukte (einschließlich Klassen und Vorlagen!) Definieren, während Sie noch nominell C schreiben!

Präprozessor-Makros bieten tatsächlich eine Turing-vollständige Sprache, die zur Kompilierungszeit ausgeführt wird. Eines der beeindruckenden (und etwas beängstigenden) Beispiele hierfür ist auf der C ++ - Seite vorbei: Die Boost-Präprozessorbibliothek verwendet den C99 / C ++ 98- Präprozessor, um (relativ) sichere Programmierkonstrukte zu erstellen, die dann auf die zugrunde liegenden Deklarationen und den Code erweitert werden Sie geben ein, ob C oder C ++.

In der Praxis würde ich empfehlen, die Präprozessorprogrammierung als letzten Ausweg zu betrachten, wenn Sie nicht den Spielraum haben, Konstrukte auf hoher Ebene in sichereren Sprachen zu verwenden. Aber manchmal ist es gut zu wissen, was Sie tun können, wenn Ihr Rücken an der Wand steht und die Wiesel sich nähern ...!


1
Warum ist es besser, den Präprozessor zum Definieren von Datenstrukturen zu verwenden, als eine Struktur zu verwenden? Und sicherlich ist der Punkt, an dem Sie anfangen, Klassen und Vorlagen mit dem Präprozessor zu definieren, der Punkt, an dem Sie möglicherweise C ++ oder eine andere Sprache mit Unterstützung für diese Konstrukte in Betracht ziehen.
Jack Ryan

Hängt davon ab, welche Freiheitsgrade Sie haben möchten: Mit Makros können Sie eine ganze Reihe von Datenstrukturen konsistent erstellen. Und Sie haben ganz recht: Es ist ein letzter Ausweg, wie ich schrieb. Aber manchmal muss man mit den Werkzeugen arbeiten, die man bekommt.
Pontus Gagge

1
Sie können tatsächlich Strukturen und andere Konstrukte verwenden und dann mit dem Präprozessor
Andrea

2
Hier finden Sie einen Beitrag zur Verwendung von C-Makros, um die Verwendung der Datenstruktur besser lesbar und verwaltbar zu machen. - Polymorphe Datenstrukturen unter Verwendung von C-Makros - Coredump
Coredump

7

Von Computer-Dummheiten :

Ich habe diesen Code-Auszug in vielen Freeware-Gaming-Programmen für UNIX gesehen:

/ *
* Bitwerte.
* /
#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 B_
#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
#define2_
#define BIT_22 4194304
#define BIT_23 8388608
#define BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108827
#define
#define BIT_28 268435456
#define BIT_29 536870912
#define BIT_30 1073741824
#define BIT_31 2147483648

Ein viel einfacherer Weg, dies zu erreichen, ist:

#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_29 0x20000000
#define B00

Noch einfacher ist es, den Compiler die Berechnungen durchführen zu lassen:

# BIT_0 definieren (1)
# BIT_1 definieren (1 << 1)
# BIT_2 definieren (1 << 2)
# BIT_3 definieren (1 << 3)
# BIT_4 definieren (1 << 4)
...
# BIT_28 definieren (1 << 28)
#define BIT_29 (1 << 29)
#define BIT_30 (1 << 30)
#define BIT_31 (1 << 31)

Aber warum sollte man sich die Mühe machen, 32 Konstanten zu definieren? Die C-Sprache hat auch parametrisierte Makros. Alles was Sie wirklich brauchen ist:

# BIT (x) definieren (1 << (x))

Wie auch immer, ich frage mich, ob der Typ, der den Originalcode geschrieben hat, einen Taschenrechner verwendet oder einfach alles auf Papier berechnet hat.

Dies ist nur eine mögliche Verwendung von Makros.


5

Einer der Fälle, in denen Makros wirklich glänzen, ist die Codegenerierung mit ihnen.

Ich habe an einem alten C ++ - System gearbeitet, das ein Plugin-System mit seiner eigenen Methode zum Übergeben von Parametern an das Plugin verwendete (unter Verwendung einer benutzerdefinierten kartenähnlichen Struktur). Einige einfache Makros wurden verwendet, um mit dieser Eigenart fertig zu werden, und ermöglichten es uns, echte C ++ - Klassen und -Funktionen mit normalen Parametern in den Plugins ohne allzu große Probleme zu verwenden. Der gesamte Klebercode wird von Makros generiert.


5

Ich werde zu dem hinzufügen, was bereits gesagt wurde.

Da Makros mit Textersetzungen arbeiten, können Sie sehr nützliche Dinge tun, die mit Funktionen nicht möglich wären.

Hier einige Fälle, in denen Makros wirklich nützlich sein können:

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

Dies ist ein sehr beliebtes und häufig verwendetes Makro. Dies ist sehr praktisch, wenn Sie beispielsweise ein Array durchlaufen müssen.

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

Hier spielt es keine Rolle, ob ein anderer Programmierer ader Dekleration fünf weitere Elemente hinzufügt. Die forSchleife wird immer alle Elemente.

Die Funktionen der C-Bibliothek zum Vergleichen von Speicher und Zeichenfolgen sind recht hässlich.

Du schreibst:

char *str = "Hello, world!";

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

oder

char *str = "Hello, world!";

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

Um zu überprüfen, ob strPunkte auf "Hello, world". Ich persönlich denke, dass diese beiden Lösungen ziemlich hässlich und verwirrend aussehen (insbesondere!strcmp(...) ) .

Hier sind zwei nette Makros, die einige Leute (einschließlich I) verwenden, wenn sie Zeichenfolgen oder Speicher mit strcmp/ vergleichen müssen 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)

Jetzt können Sie den Code wie folgt schreiben:

char *str = "Hello, world!";

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

Hier ist die Absicht viel klarer!

Dies sind Fälle, in denen Makros für Dinge verwendet werden, die Funktionen nicht ausführen können. Makros sollten nicht zum Ersetzen von Funktionen verwendet werden, sie haben jedoch andere nützliche Verwendungszwecke.


Tolle Beispiele!
rollt

4

Angesichts der Kommentare in Ihrer Frage wissen Sie möglicherweise nicht genau, dass das Aufrufen einer Funktion einen erheblichen Aufwand bedeuten kann. Die Parameter und Schlüsselregister müssen möglicherweise auf dem Weg nach innen auf den Stapel kopiert und auf dem Weg nach draußen abgewickelt werden. Dies gilt insbesondere für die älteren Intel-Chips. Mit Makros kann der Programmierer die Abstraktion einer Funktion (fast) beibehalten, aber den kostspieligen Overhead eines Funktionsaufrufs vermeiden. Das Inline-Schlüsselwort ist ratsam, aber der Compiler macht es möglicherweise nicht immer richtig. Der Ruhm und die Gefahr von 'C' besteht darin, dass Sie den Compiler normalerweise nach Ihrem Willen biegen können.

In Ihrem Brot und Butter ist die tägliche Anwendungsprogrammierung dieser Art der Mikrooptimierung (Vermeidung von Funktionsaufrufen) im Allgemeinen schlechter als nutzlos. Wenn Sie jedoch eine zeitkritische Funktion schreiben, die vom Kernel eines Betriebssystems aufgerufen wird, dann es kann einen großen Unterschied machen.


1
Ich kann verstehen, dass Inlining als Optimierung nützlich sein kann, aber ich kann nicht verstehen, warum der Präprozessor dafür benötigt wird. Warum funktioniert das Inline-Schlüsselwort nicht immer? Die Verwendung des Präprozessors für "wirklich inline" scheint ein häufiger Hack zu sein, der durch einen Wechsel des Compilers besser bedient werden kann.
Jack Ryan

1
Das Inlining von Code ist mit Kosten verbunden. Der Compiler hat einige Faustregeln, um den Nutzen (schnellerer Code) gegen die Kosten (dickerer Code) abzuwägen. Wenn die Faustregel in Ihrem Fall falsch ist, können Sie mit Makros den Compiler aus dem Weg räumen, um das gewünschte Ergebnis zu erzielen.
Charles E. Grant

1
Es gibt Probleme mit der Abwärtskompatibilität und historischen Unfällen. Es gibt Milliarden von Zeilen C-Code, die nicht geändert werden sollen. Daher müssen Änderungen an der C-Sprache zu diesem Zeitpunkt relativ klein und so abwärtskompatibel wie möglich sein.
Charles E. Grant

4

Im Gegensatz zu regulären Funktionen können Sie den Ablauf (wenn, während, für, ...) in Makros steuern. Hier ist ein Beispiel:

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

3

Dies ist gut geeignet, um Code einzubinden und den Aufwand für Funktionsaufrufe zu vermeiden. Sie können es auch verwenden, wenn Sie das Verhalten später ändern möchten, ohne viele Stellen zu bearbeiten. Es ist nicht nützlich für komplexe Dinge, aber für einfache Codezeilen, die Sie inline möchten, ist es nicht schlecht.


2

Durch Nutzung der Textmanipulation des C-Präprozessors kann das C-Äquivalent einer polymorphen Datenstruktur konstruiert werden. Mit dieser Technik können wir eine zuverlässige Toolbox primitiver Datenstrukturen erstellen, die in jedem C-Programm verwendet werden können, da sie die C-Syntax und nicht die Besonderheiten einer bestimmten Implementierung nutzen.

Ausführliche Erläuterungen zur Verwendung von Makros zum Verwalten der Datenstruktur finden Sie hier - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html


2

Mit Makros können Sie kopierte Fragmente entfernen, die Sie auf keine andere Weise entfernen können.

Zum Beispiel (der echte Code, die Syntax des VS 2010-Compilers):

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

Hier übergeben Sie einen Feldwert unter demselben Namen an eine Skript-Engine. Ist das kopiert? Ja. DisplayNamewird als Zeichenfolge für ein Skript und als Feldname für den Compiler verwendet. Ist das schlecht? Ja. Wenn Sie umgestalten, codieren Sie und benennen LocalNamein umRelativeFolderName (wie ich) und vergessen, dasselbe mit der Zeichenfolge zu tun (wie ich), funktioniert das Skript auf eine Weise, die Sie nicht erwarten (in meinem Beispiel hängt es sogar davon ab Haben Sie vergessen, das Feld in einer separaten Skriptdatei umzubenennen, aber wenn das Skript für die Serialisierung verwendet wird, ist dies ein 100% iger Fehler.

Wenn Sie hierfür ein Makro verwenden, ist kein Platz für den Fehler:

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

Leider öffnet dies eine Tür für andere Arten von Fehlern. Sie können einen Tippfehler beim Schreiben des Makros machen und sehen niemals einen verdorbenen Code, da der Compiler nicht zeigt, wie er nach der gesamten Vorverarbeitung aussieht. Jemand anderes könnte den gleichen Namen verwenden (deshalb "veröffentliche" ich Makros so schnell wie möglich mit #undef). Verwenden Sie es also mit Bedacht. Wenn Sie eine andere Möglichkeit sehen, kopierten Code (z. B. Funktionen) zu entfernen, verwenden Sie diese Methode. Wenn Sie feststellen, dass das Entfernen von kopiertem Code mit Makros das Ergebnis nicht wert ist, behalten Sie den kopierten Code bei.


1

Einer der offensichtlichen Gründe ist, dass durch die Verwendung eines Makros der Code zur Kompilierungszeit erweitert wird und Sie einen Pseudofunktionsaufruf ohne den Aufrufaufwand erhalten.

Andernfalls können Sie es auch für symbolische Konstanten verwenden, sodass Sie nicht denselben Wert an mehreren Stellen bearbeiten müssen, um eine kleine Sache zu ändern.


0

Makros .. für den Fall, dass Ihr & # (* $ & Compiler sich einfach weigert, etwas einzubinden.

Das sollte ein Motivationsplakat sein, oder?

In aller Ernsthaftigkeit, Google Präprozessor Missbrauch (möglicherweise wird eine ähnliche SO-Frage als das Ergebnis Nr. 1 angezeigt). Wenn ich ein Makro schreibe, das über die Funktionalität von assert () hinausgeht, versuche ich normalerweise zu sehen, ob mein Compiler tatsächlich eine ähnliche Funktion einbindet.

Andere werden gegen die Verwendung von #if für die bedingte Kompilierung argumentieren. Sie möchten lieber, dass Sie:

if (RUNNING_ON_VALGRIND)

eher, als

#if RUNNING_ON_VALGRIND

.. für Debugging-Zwecke, da Sie in einem Debugger das if (), aber nicht #if sehen können. Dann tauchen wir in #ifdef vs #if ein.

Wenn es weniger als 10 Codezeilen gibt, versuchen Sie es zu inline. Wenn es nicht inline sein kann, versuchen Sie es zu optimieren. Wenn es zu dumm ist, um eine Funktion zu sein, erstellen Sie ein Makro.


0

Obwohl ich kein großer Fan von Makros bin und aufgrund meiner aktuellen Aufgaben nicht mehr viel C schreibe, ist so etwas (das offensichtlich einige Nebenwirkungen haben könnte) praktisch:

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

Jetzt habe ich so etwas seit Jahren nicht mehr geschrieben, aber solche 'Funktionen' waren überall im Code enthalten, den ich früher in meiner Karriere gepflegt habe. Ich denke, die Erweiterung könnte als zweckmäßig angesehen werden.


2
int ohNo = MIN(functionWithSideEffect(x), y++);
Dour High Arch

0

Ich habe niemanden gesehen, der dies so erwähnt hat, was Funktionen wie Makros betrifft, z.

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

Im Allgemeinen wird aus vielen Gründen empfohlen, die Verwendung von Makros zu vermeiden, wenn dies nicht erforderlich ist. Die Lesbarkeit ist das Hauptanliegen. Damit:

Wann sollten Sie diese über eine Funktion verwenden?

Fast nie, da es eine besser lesbare Alternative gibt inline, siehe https://www.greenend.org.uk/rjk/tech/inline.html oder http://www.cplusplus.com/articles/2LywvCM9/ (die zweite) link ist eine C ++ - Seite, aber der Punkt gilt meines Wissens für c-Compiler.

Der kleine Unterschied besteht nun darin, dass Makros vom Vorprozessor und Inline vom Compiler verarbeitet werden, aber heutzutage gibt es keinen praktischen Unterschied.

Wann ist es angebracht, diese zu verwenden?

Für kleine Funktionen (maximal zwei oder drei Liner). Das Ziel ist es, während der Laufzeit eines Programms einen gewissen Vorteil zu erzielen, da Funktionen wie Makros (und Inline-Funktionen) Code-Ersetzungen sind, die während der Vorverarbeitung (oder Kompilierung im Fall von Inline) durchgeführt werden und keine realen Funktionen sind, die im Speicher leben. Es gibt also keinen Overhead für Funktionsaufrufe (weitere Details auf den verlinkten Seiten).

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.