webentwicklung-frage-antwort-db.com.de

Wofür sind C-Makros nützlich?

Ich habe ein bisschen C geschrieben, und ich 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 einsetzen. Diejenigen, die mir begegneten und die intuitiv und leicht verständlich waren, waren immer wie kleine Minifunktionen, daher habe ich mich immer gefragt, warum sie nicht nur Funktionen sind.

Ich verstehe die Notwendigkeit, verschiedene Build-Typen für Debugging- oder Cross-Plattform-Builds im Präprozessor zu definieren, aber die Möglichkeit, beliebige Substitutionen zu definieren, scheint nur nützlich zu sein, um eine bereits schwierige Sprache noch schwieriger zu verstehen.

Warum wurde ein derart komplexer Präprozessor für C eingeführt? Und hat jemand ein Beispiel für die Verwendung, das mich verstehen lässt, warum es immer noch zu anderen Zwecken als zu einfachen Zwecken verwendet wird, wenn der #Debug-Stil bedingte Kompilierungen verwendet?

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 "wirklich Inline dieses Codes" bedeutet (abgesehen von dem Code, der vor Inline geschrieben wurde). Ich verstehe auch nicht die Idee, die erwähnt wurde, "wenn es zu dumm ist, um in eine Funktion eingefügt zu werden". Sicherlich wird jeder Code, der eine Eingabe übernimmt und eine Ausgabe erzeugt, am besten in eine Funktion eingefügt. Ich denke, ich bekomme es vielleicht nicht, weil ich nicht an die Mikrooptimierungen des Schreibens C gewöhnt bin, aber der Präprozessor fühlt sich einfach wie eine komplexe Lösung für ein paar einfache Probleme an.

69
Jack Ryan

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

Das scheint sich schlecht auf die Benennung der Makros zu beziehen. Ich würde davon ausgehen, dass Sie den Präprozessor nicht emulieren müssten, wenn es sich um ein log_function_entry()-Makro handelt. 

Diejenigen, die mir begegneten und die intuitiv und leicht verständlich waren, waren immer wie kleine Minifunktionen, daher habe ich mich immer gefragt, warum sie nicht einfach nur funktionieren.

Normalerweise sollte dies der Fall sein, es sei denn, sie müssen mit generischen Parametern arbeiten. 

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

funktioniert für jeden Typ mit einem <-Operator.

Makros bieten mehr als nur Funktionen. Sie können 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 des Makros verweisen. 

In C99 können Sie mithilfe von Makros auch verschiedene Funktionen wie printf aufrufen

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

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

In welchem ​​Format funktioniert das wie bei printf. Wenn der Guard wahr ist, gibt er die Nachricht zusammen mit der Datei und der Zeilennummer aus, mit der die Nachricht gedruckt wurde. Wenn es sich um einen Funktionsaufruf handelt, kennt er die Datei und die Zeile, von der Sie ihn aufgerufen haben, nicht, und die Verwendung einer vaprintf wäre etwas mehr Arbeit. 

53
Pete Kirkham

Dieser Auszug fasst meine Sicht auf die Angelegenheit so ziemlich zusammen, dass er verschiedene Verwendungsmöglichkeiten von C-Makros vergleicht und wie diese in D implementiert werden.

von DigitalMars.com kopiert

Zurück, als C erfunden wurde, Compiler Technologie war primitiv. Installieren eines Text-Makro-Präprozessor auf der Vorderseite Das Ende war ein einfacher und einfacher Weg viele leistungsstarke Funktionen hinzufügen. Das zunehmende Größe und Komplexität von Programme haben gezeigt, dass diese Features kommen mit vielen inhärenten Probleme. D hat keine Präprozessor; aber D liefert mehr skalierbare Mittel, um dasselbe zu lösen Probleme.

Makros

Präprozessor-Makros fügen C leistungsstarke Funktionen und Flexibilität hinzu. 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 Ausschnitt über .h-Dateien, verschachtelten Code usw. Wenn #include zehntausende Zeilen von Makrodefinitionen enthält, wird es problematisch, versehentliche Makroerweiterungen zu vermeiden.
  • Makros sind dem Debugger nicht bekannt. Der Versuch, ein Programm mit symbolischen Daten zu debuggen, wird durch den Debugger untergraben, der nur die Makroerweiterungen kennt und nicht die Makros selbst.
  • Makros machen es unmöglich, Quellcode zu kennzeichnen, da eine frühere Makroänderung Token beliebig wiederherstellen 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, dieses Problem zu lösen, wurden mit Vorlagen in C++ eingeführt.)
  • Makros werden immer noch verwendet, um Defizite in der Ausdrucksfähigkeit der Sprache auszugleichen, beispielsweise für "Wrapper" um Header-Dateien.

Hier finden Sie eine Auflistung der gängigen Verwendungen von Makros und der entsprechenden Funktion in D:

  1. Literalkonstanten definieren:

    • Die C-Präprozessor-Methode

      #define VALUE 5
      
    • Der D-Weg

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

    • Die C-Präprozessor-Methode

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

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

    • Die C-Präprozessor-Methode

      #ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #endif
      #ifndef _CRTAPI2
      #define _CRTAPI2 __cdecl
      #endif
      
      int _CRTAPI2 func();
      
    • Der D-Weg

      Aufrufkonventionen können in Blöcken angegeben werden, sodass keine Änderung für jede Funktion erforderlich ist:

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

    • Die C-Präprozessor-Methode

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

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

      D ermöglicht Deklarationen von Symbolen, die Aliasnamen 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
Brad Gilbert

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

Sie sind auch sehr nützlich, um "funktionsähnliche" Ausdrücke zu schreiben, die "polymorph" oder "überladen" sind. z.B. ein maximales 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 nicht zu vergessen das bedingte Kompilieren und das Einschließen von Dateien (die auch Teil der Makrosprache sind) ...

15
fortran

Mithilfe von Makros kann das Programmverhalten während der Kompilierungszeit geändert werden. Bedenken Sie:

  • C-Konstanten ermöglichen die Korrektur des Programmverhaltens zur Entwicklungszeit
  • C-Variablen erlauben das Ändern des Programmverhaltens zur Ausführungszeit
  • C-Makros ermöglichen das Ändern des Programmverhaltens zur Kompilierzeit

Bei der Kompilierung bedeutet dies, dass nicht verwendeter Code nicht in die Binärdatei übernommen wird und der Buildprozess die Werte ändern kann, solange er in den Makro-Präprozessor integriert ist. Beispiel: make Arch = arm (setzt voraus, dass die Makrodefinition als cc -DARCH = arm definiert wird)

Einfache Beispiele: (Von glibc limits.h definieren Sie 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 Kompilierzeit, ob 32 oder 64 Bit kompiliert werden. Bei einer Multilib-Toolchain können die Parameter -m32 und -m64 die Bitgröße automatisch ändern.

(POSIX-Versionsanfrage)

#define _POSIX_C_SOURCE 200809L

Anforderungen während der Kompilierungszeit POSIX 2008-Unterstützung. Die Standardbibliothek unterstützt möglicherweise viele (inkompatible) Standards. Mit dieser Definition werden jedoch die korrekten Funktionsprototypen bereitgestellt (Beispiel: getline (), no gets () usw.). Wenn die Bibliothek den Standard nicht unterstützt, kann dies während der Kompilierzeit einen Fehler # geben, 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. Kann beispielsweise mit -DLIBRARY_PATH =/home/user/lib geändert werden. Wenn dies ein const char * wäre, wie würden Sie es beim Kompilieren konfigurieren?

(pthread.h, komplexe Definitionen zur Kompilierzeit)

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

Große Textstücke, die sonst nicht vereinfacht werden, können deklariert werden (immer zur Kompilierzeit). Dies ist nicht möglich mit Funktionen oder Konstanten (zur Kompilierzeit).

Um wirklich Komplikationen zu vermeiden und schlechte Codierungsstile zu vermuten, werde ich nicht ein Beispiel für Code geben, 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 dass die Kompilierung aufgrund fehlender Schnittstellen unterbrochen wird.

Bedenken Sie zum Schluss 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 jetzt fragen, ist es möglich, alle Makro-Konstantendefinitionen und Funktionsaufrufe durch geeignete Definitionen zu ersetzen? Die Antwort lautet "Ja", aber es wird nicht einfach die Notwendigkeit zur Änderung des Programmverhaltens während des Kompilierens verschwinden lassen. Der Präprozessor wäre noch erforderlich.

12
hdante

Denken Sie daran, dass Makros (und der Vorprozessor) aus den ersten 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 die einzige Möglichkeit, etwas zu forcieren. 

Makros sind die einzige Möglichkeit, solche Tricks auszuführen, wie das Einfügen der Datei und der Zeile in String-Konstanten zur Kompilierzeit.

Heutzutage sind viele der Dinge, die Makros der einzige Weg waren, besser durch neuere Mechanismen zu handhaben. Aber sie haben immer noch ihren Platz.

11
Michael Kohne

Neben dem Inlining für Effizienz und der bedingten Kompilierung können Makros verwendet werden, um das Abstraktionsniveau von Low-Level-C-Code zu erhöhen. C isoliert Sie nicht wirklich vor den winzigen Details des Speicher- und Ressourcenmanagements und dem genauen Layout der Daten und unterstützt nur sehr eingeschränkte Formen des Verdeckens von Informationen und andere Mechanismen zur Verwaltung großer Systeme. Mit Makros können Sie nicht mehr nur die Basiskonstrukte in der C-Sprache verwenden: Sie können Ihre eigenen Datenstrukturen und Codierungskonstrukte (einschließlich Klassen und Vorlagen!) Definieren, während Sie gleichzeitig C schreiben! 

Preprozessor-Makros bieten tatsächlich eine Turing-complete - Sprache, die zur Kompilierzeit ausgeführt wird. Eines der eindrucksvollen (und etwas beängstigenden) Beispiele ist auf der C++ - Seite vorbei: Die Boost-Präprozessor - Bibliothek verwendet den C99 / C++ 98 - Präprozessor, um (relativ) sicher zu bauen Programmierkonstrukte, die dann auf alle darunter liegenden Deklarationen und den von Ihnen eingegebenen Code (C oder C++) erweitert werden. 

In der Praxis würde ich empfehlen, die Präprozessor-Programmierung als letzten Ausweg zu betrachten, wenn Sie nicht die Möglichkeit haben, High-Level-Konstrukte in sichereren Sprachen zu verwenden. Aber manchmal ist es gut zu wissen, was Sie tun können, wenn Ihr Rücken gegen die Wand gerichtet ist und die Wiesel sich schließen ...! 

8
Pontus Gagge

Von Computer-Dummheiten :

Ich habe diesen Code-Auszug in vielen Freeware-Spielprogrammen 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 BIT_11 2048
#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
#define BIT_21 2097152
#define BIT_22 4194304
#define BIT_23 8388608
#define BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108864
#define BIT_27 134217728
#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_30 0x40000000
#define BIT_31 0x80000000 

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

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

Aber warum sollte man sich nur 32 Konstanten definieren lassen? Die C-Sprache hat auch Makros parametrisiert. Alles was Sie wirklich brauchen, ist:

#define BIT (x) (1 << (x))

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

Dies ist nur eine mögliche Verwendung von Makros.

7

Ich werde hinzufügen, was bereits gesagt wurde.

Da Makros Text ersetzen, 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;
}

Dabei spielt es keine Rolle, ob ein anderer Programmierer a in der Deklaration fünf weitere Elemente hinzufügt. Die for-Schleife durchläuft always alle Elemente.

Die Funktionen der C-Bibliothek zum Vergleichen von Speicher und Strings sind ziemlich 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 str auf "Hello, world" zeigt. Ich persönlich denke, dass diese beiden Lösungen ziemlich hässlich und verwirrend aussehen (vor allem !strcmp(...)). 

Hier sind zwei nette Makros, die einige Leute (einschließlich ich) verwenden, wenn sie Zeichenfolgen oder Speicher mithilfe von strcmp/memcmp vergleichen müssen:

/* 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 verwendet werden, wenn Funktionen nicht ausgeführt werden können. Makros sollten nicht als Ersatz für Funktionen verwendet werden, sie sind jedoch für andere Zwecke geeignet.

5
wefwefa3

Makros leuchten vor allem dann, wenn mit ihnen Code-Generierung durchgeführt wird.

Ich arbeitete an einem alten C++ - System, das ein Plug-In-System verwendete, um Parameter an das Plug-In zu übergeben (Verwendung einer benutzerdefinierten Map-ähnlichen Struktur). Einige einfache Makros wurden verwendet, um mit dieser Eigenart umgehen zu können, und erlaubten uns, echte C++ - Klassen und -Funktionen mit normalen Parametern in den Plugins zu verwenden, ohne allzu große Probleme zu haben. Der gesamte Klebercode wird von Makros generiert.

5

In Anbetracht der Kommentare in Ihrer Frage ist Ihnen möglicherweise nicht bewusst, dass der Aufruf einer Funktion einen erheblichen Mehraufwand verursachen kann. Die Parameter und Schlüsselregister müssen möglicherweise auf den Einstieg in den Stapel kopiert werden, und der Stapel wird auf dem Ausweg abgewickelt. Dies gilt insbesondere für die älteren Intel-Chips. Makros lassen den Programmierer (fast) die Abstraktion einer Funktion beibehalten, vermeiden jedoch den kostspieligen Aufwand eines Funktionsaufrufs. Das Inline-Schlüsselwort ist eine Empfehlung, aber der Compiler kann es nicht immer richtig machen. Der Ruhm und die Gefahr von 'C' ist, dass Sie den Compiler normalerweise nach Ihrem Willen beugen können.

Im Alltag ist diese Art der Mikrooptimierung (das Vermeiden von Funktionsaufrufen) in der täglichen Anwendung in der Regel schlechter als nutzlos, wenn Sie jedoch eine zeitkritische Funktion schreiben, die vom Kern eines Betriebssystems aufgerufen wird Es kann einen großen Unterschied machen.

4

Es ist gut für das Inlining von Code und das Vermeiden von Funktionsaufrufen. Sie können es auch verwenden, wenn Sie das Verhalten später ändern möchten, ohne viele Orte bearbeiten zu müssen. Dies ist nicht nützlich für komplexe Dinge, aber für einfache Codezeilen, die Sie integrieren möchten, ist es nicht schlecht.

3
Arkaitz Jimenez

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
alexpinho98

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

Dies ist der Ort, an dem Sie einen Feldwert unter demselben Namen an eine Skript-Engine übergeben. Ist das kopiert? Ja. DisplayName wird als Zeichenfolge für ein Skript und als Feldname für den Compiler verwendet. Ist das schlecht? Ja. Wenn Sie den Code umwandeln und LocalName in RelativeFolderName umbenennen (wie ich es getan habe) und vergessen, das Gleiche mit der Zeichenfolge zu tun (wie ich es tat), funktioniert das Skript auf eine Weise, die Sie nicht erwarten (in meinem Beispiel tatsächlich) hängt davon ab, ob Sie vergessen haben, das Feld in einer separaten Skriptdatei umzubenennen, aber wenn das Skript für die Serialisierung verwendet wird, wäre dies ein 100% iger Fehler.

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

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 sich dadurch eine Tür für andere Arten von Fehlern. Sie können einen Tippfehler beim Schreiben des Makros eingeben und sehen niemals einen verdorbenen Code, da der Compiler nicht zeigt, wie er nach der gesamten Vorverarbeitung aussieht. Jemand anderes könnte denselben Namen verwenden (deshalb "gebe ich" so schnell wie möglich Makros mit #undef frei). Also benutze es weise. Wenn Sie einen anderen Weg sehen, um kopierten Code (z. B. Funktionen) zu entfernen, verwenden Sie diesen Weg. Wenn Sie feststellen, dass es sich nicht lohnt, den kopierten Code mit Makros zu löschen, behalten Sie den kopierten Code.

2
noober

Durch die Verwendung 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 aus primitiven 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 Informationen 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
coredump

Ein offensichtlicher Grund ist, dass durch die Verwendung eines Makros der Code zur Kompilierzeit erweitert wird und Sie einen Pseudo-Funktionsaufruf ohne Aufrufaufruf erhalten. 

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

1
sykora

Makros ... für den Fall, dass Ihr & # (* $ & -Compiler) etwas ablehnt.

Das sollte ein Motivationsposter sein, nein?

Im Ernstfall google Präprozessor-Missbrauch (Sie können eine ähnliche SO - Frage als Ergebnis # 1 sehen). 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 einlöst.

Andere sprechen sich gegen die Verwendung von #if für die bedingte Kompilierung aus. Sie möchten Sie lieber:

if (RUNNING_ON_VALGRIND)

eher, als

#if RUNNING_ON_VALGRIND

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

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

0
Tim Post

Ich bin zwar kein großer Fan von Makros und neige nicht mehr dazu, viel C zu schreiben, basierend auf meinem aktuellen Tasking, aber so etwas (was offensichtlich einige Nebenwirkungen haben könnte) ist praktisch:

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

Jetzt habe ich so etwas seit Jahren nicht mehr geschrieben, aber solche 'Funktionen' standen im gesamten Code, den ich früher in meiner Karriere beibehalten habe. Ich denke, die Erweiterung könnte als bequem betrachtet werden.

0
itsmatt

Ich habe niemanden gesehen, der dies so erwähnte, in Bezug auf Funktionen wie Makros, zB:

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

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

Wann sollten Sie diese über eine Funktion verwenden?

Fast nie, da es eine besser lesbare Alternative gibt, nämlich inline, siehe https://www.greenend.org.uk/rjk/tech/inline.html oder http://www.cplusplus.com/articles/2LywvCM9/ (der 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 Pre-Prozessor und Inline vom Compiler gehandhabt 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, einen gewissen Vorteil während der Laufzeit eines Programms zu erzielen, da Funktionen wie Makros (und Inline-Funktionen) Code-Ersetzungen sind, die während der Vorverarbeitung (oder beim Kompilieren im Inline-Modus) vorgenommen werden und keine echten Funktionen im Speicher sind. Es gibt also keinen Funktionsaufruf-Overhead (mehr Details auf den verlinkten Seiten).

0
äli