Implementieren Sie ein generisches Swap-Makro in C [duplicate]

Lesezeit: 10 Minuten

Benutzer-Avatar
Mohit Duper

Mögliches Duplikat:

Gibt es ein Äquivalent zu std::swap() in c

Hallo Leute,

Ich habe versucht, ein Problem zu schreiben, um ein generisches Swap-Makro in C zu schreiben, und mein Makro sieht so aus:

#define swap(x,y) { x = x + y; y = x - y; x = x - y; }

Es funktioniert gut für ganze Zahlen und Floats, aber ich bin mir nicht sicher, ob es einen Haken gibt. Was ist, wenn sie mit generischem Makro das Austauschen von Zeigern, Zeichen usw. meinen? Kann mir jemand beim Schreiben eines generischen Makros zum Austauschen aller Eingaben helfen?

Vielen Dank

  • Es wird aufgrund von Integer-Überlaufproblemen nicht funktionieren. Die Antwort von Paul R sieht so aus, wie Sie es wollen.

    – nmichaels

    20. Oktober 2010 um 21:21 Uhr

  • Dies wird nicht für alle Schwimmer funktionieren. Vorstellen float x = 1e9; float y = 1.0f;. Außerdem ist die Addition für Zeiger nicht definiert.

    – Oliver Charlesworth

    20. Oktober 2010 um 21:21 Uhr

  • Und warum machst du nicht einfach den normalen Code mit einer temporären Variable? Ich bin mir ziemlich sicher, dass der Temp-Variablencode mindestens so schnell, wenn nicht sogar schneller ist.

    – CodesInChaos

    20. Oktober 2010 um 21:40 Uhr

  • @CodeInChaos: Da es sich um ein Makro und nicht um eine tatsächliche Vorlage handelt, weiß er nicht, welchen Typ er für die temporäre Variable erstellen soll.

    – Welpe

    21. Oktober 2010 um 8:33 Uhr

  • Obwohl dies in der Tat eng mit stackoverflow.com/questions/2637856/… verwandt sein könnte, halte ich es persönlich nicht für ein Duplikat – ich habe für die Wiedereröffnung gestimmt, zumal die bisherigen Antworten und Diskussionen sehr aufschlussreich sind.

    – Gregor S

    12. November 2013 um 9:55 Uhr

Benutzer-Avatar
Adam

Dies funktioniert nur mit ganzen Zahlen gut.

Für Schwimmer wird es fehlschlagen (zB versuchen Sie es mit einem sehr großen und einem sehr kleinen Schwimmer).

Ich würde etwas wie folgt vorschlagen:

#define swap(x,y) do \ 
   { unsigned char swap_temp[sizeof(x) == sizeof(y) ? (signed)sizeof(x) : -1]; \
     memcpy(swap_temp,&y,sizeof(x)); \
     memcpy(&y,&x,       sizeof(x)); \
     memcpy(&x,swap_temp,sizeof(x)); \
    } while(0)

memcpy ist ziemlich optimiert, wenn die zu kopierende Menge zum Zeitpunkt der Kompilierung bekannt ist. Außerdem ist es nicht erforderlich, manuell einen Typnamen zu übergeben oder Compiler-spezifische Erweiterungen zu verwenden.

  • +1 für die einzig richtige Antwort, dann muss der Typ nicht als Argument an das Makro übergeben werden.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    20. Oktober 2010 um 21:26 Uhr

  • Warum nicht das machen swap_temp[sizeof(x) == sizeof(y) ? sizeof(x) : -1]. Sie erhalten einen generischen Puffer und prüfen zur Kompilierzeit, ob sie gleich sind.

    – GManNickG

    20. Oktober 2010 um 21:26 Uhr

  • Dies wird stillschweigend fehlschlagen, wenn mein Variablenname lautet swap_temp.

    – JaredPar

    20. Oktober 2010 um 21:45 Uhr

  • @GMan, ja und hier ist ein echtes Szenario, in dem ich gesehen habe, dass es passiert ist (nicht speziell mit Swap, sondern mit anderen Makros, die zu Konflikten führten). Entwickler A: schreibt die Lösung mit dem von Ihnen angegebenen Namen. Entwickler B: sieht die Lösung von Entwickler A und sagt „wow, toller temporärer Name“ und verwendet sie erneut. Entwickler C verwendet das Makro von B in Kombination mit dem von A. Boom.

    – JaredPar

    20. Oktober 2010 um 23:07 Uhr


  • Die Frage war nicht “wie man ein Swap-Makro implementiert”, sondern “wie man ein Swap-Makro in C implementiert”. In C++ implementieren Sie kein Swap-Makro. So einfach ist das. Diese Frage betrifft oder bezieht sich nicht auf C++. Über C/C++ zu sprechen, insbesondere in diesem spezifischen Kontext, in dem C++ einen völlig anderen Ansatz für dasselbe Problem verfolgt, ist unglaublich falsch.

    – Welpe

    21. Oktober 2010 um 22:20 Uhr

Benutzer-Avatar
Paul R

Sie können so etwas tun:

#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)

die Sie dann wie folgt aufrufen würden:

SWAP(a, b, int);

oder:

SWAP(x, y, float);

Wenn Sie gerne gcc-spezifische Erweiterungen verwenden, können Sie dies folgendermaßen verbessern:

#define SWAP(x, y) do { typeof(x) SWAP = x; x = y; y = SWAP; } while (0)

und dann wäre es nur:

SWAP(a, b);

oder:

SWAP(x, y);

Dies funktioniert für die meisten Typen, einschließlich Zeigern.

Hier ein Testprogramm:

#include <stdio.h>

#define SWAP(x, y) do { typeof(x) SWAP = x; x = y; y = SWAP; } while (0)

int main(void)
{
    int a = 1, b = 2;
    float x = 1.0f, y = 2.0f;
    int *pa = &a;
    int *pb = &b;

    printf("BEFORE:\n");
    printf("a = %d, b = %d\n", a, b);
    printf("x = %f, y = %f\n", x, y);
    printf("pa = %p, pb = %p\n", pa, pb);

    SWAP(a, b);     // swap ints
    SWAP(x, y);     // swap floats
    SWAP(pa, pb);   // swap pointers

    printf("AFTER:\n");
    printf("a = %d, b = %d\n", a, b);
    printf("x = %f, y = %f\n", x, y);
    printf("pa = %p, pb = %p\n", pa, pb);

    return 0;
}

  • Schnell, klar, korrekt. +1

    – nmichaels

    20. Oktober 2010 um 21:22 Uhr

  • Ich bin neugierig; warum zum do{}while(0) konstruieren? Um Probleme mit der Makroerweiterung zu bekämpfen?

    – Du

    20. Oktober 2010 um 21:30 Uhr

  • @You: Es ist eine kanonische Form für Makros, die das Problem eines ungültigen anspricht ; in einem if/else-Konstrukt. Nimmst du die weg do/while (0) dann ein Ausdruck wie z if (foo) SWAP(x, y); else SWAP(a, b); wird einen Fehler erzeugen, wenn es erweitert wird if (foo) { ... }; else { ... };und die }; unmittelbar vor dem else erzeugt einen Kompilierfehler.

    – PaulR

    20. Oktober 2010 um 21:32 Uhr


  • @Paul R, Rufen Sie die Variable auf SWAPverwirrend, aber funktioniert 🙂

    – Ideengeber42

    22. März 2015 um 8:12 Uhr


  • @ideasman42: Ja, es war nicht so sehr Schatten, den dieser Kludge zu beheben versuchte – das Problem ist, dass, wenn einer der Parameter “temp” heißt und die lokale temporäre Variable auch “temp” heißt, es bricht , daher muss ein Name verwendet werden, der so eindeutig wie möglich ist. (Dies könnte natürlich sogar mit einem temporären Namen “SWAP” passieren, aber es ist höchst unwahrscheinlich, dass jemand in diesem Zusammenhang eine Variable namens “SWAP” hat.)

    – PaulR

    22. März 2015 um 8:20 Uhr


GMan startete diesen Versuch, dies in Kombination von an zu codieren inline Funktion und ein Makro. Diese Lösung setzt voraus, dass Sie einen modernen C-Compiler haben, der C99 unterstützt, da er a verwendet zusammengesetztes Literal:

inline void swap_detail(void* p1, void* p2, void* tmp, size_t pSize)
{
   memcpy(tmp, p1, pSize);
   memcpy(p1, p2, pSize);
   memcpy(p2 , tmp, pSize);
}
#define SWAP(a, b) swap_detail(&(a), &(b), (char[(sizeof(a) == sizeof(b)) ? (ptrdiff_t)sizeof(a) : -1]){0}, sizeof(a))

Diese hat folgende Eigenschaften:

  • Es wertet jeweils a und b nur einmal.
  • Es hat eine Kompilierzeitprüfung für die richtigen Größen.
  • Es gibt kein Namensproblem mit einer versteckten Variablen.
  • Die Größe der temporären Variablen wird zur Kompilierzeit berechnet, daher ist das zusammengesetzte Literal kein dynamisches Array.

Die Besetzung (ptrdiff_t) wird benötigt, damit die -1 wird nicht stillschweigend befördert SIZE_MAX.

Diese Lösung leidet noch unter zwei Nachteilen:

  1. Es ist nicht typsicher. Es überprüft nur die Größe der Typen, nicht ihre Semantik. Wenn sich die Typen unterscheiden, sagen Sie a double der Größe 8 und a uint64_tdu bist in Schwierigkeiten.

  2. Die Ausdrücke müssen das zulassen & Betreiber anwendbar sein. Daher funktioniert es nicht mit Variablen, die mit deklariert sind register
    Speicherklasse.

  • Ich mag, wie knapp es ist, ich wusste nicht, dass Sie das mit Arrays machen können.

    – GManNickG

    20. Oktober 2010 um 23:26 Uhr

  • sizeof *(1 ? &(a) : &(b)) wäre eine einfachere und typsicherere Option für das dritte Argument zu swap_detail . Gutschrift hier

    – MM

    5. November 2017 um 21:00 Uhr

Benutzer-Avatar
Herr Lama

Einfach gesagt: du kannst kein machen generisch Swap-Makro zumindest in C, nicht ohne Risiko oder Kopfschmerzen. (Siehe andere Beiträge. Die Erklärung liegt unten.)

Makros sind nett, aber das Problem, auf das Sie mit dem tatsächlichen Code stoßen, wären Datentypprobleme (wie Sie gesagt haben). Außerdem sind Makros in gewisser Weise “dumm”. Zum Beispiel:

Mit deinem Beispielmakro #define swap(x,y) { x = x + y; y = x - y; x = x - y; }, swap(++x, y) verwandelt sich in { ++x = ++x + y; y = ++x - y; ++x = ++x - y;}.

Wenn du gerannt bist int x = 0, y = 0; swap(++x, y); du würdest bekommen x=2, y=3 Anstatt von x=0, y=0. Darüber hinaus könnten einige lästige Fehler auftreten, wenn eine der temporären Variablen in Ihrem Makro in Ihrem Code auftaucht.

Die gesuchten Funktionen wurden in C++ als Vorlagen eingeführt. Das Beste, was Sie in C erreichen können, ist die Verwendung einer Inline-Funktion für jeden vorstellbaren Datentyp oder eines anständig komplexen Makros (siehe vorherige Makroprobleme und vorherige Posts).

So sieht die Lösung mit Vorlagen in C++ aus:

template<typename T>
inline void swap(T &x, T &y)
{
    T tmp = x;
    x = y; y = tmp;
}

In C bräuchten Sie so etwas wie:

inline void swap_int(int *x, int *y) { /* Code */ }
inline void swap_char(char *x, char *y) { /* Code */ }
// etc.

oder (wie einige Male erwähnt) ein ziemlich komplexes Makro, das gefährlich sein kann.

Dies funktioniert nicht unbedingt gut für int nach Stand. Stellen Sie sich den Fall vor, wo x und y war INT_MAX und INT_MAX-1 beziehungsweise. Die erste Additionsanweisung würde zu einem vorzeichenbehafteten Überlauf führen, der vom Standard nicht definiert ist.

Eine zuverlässigere Implementierung des Swap-Makros für int wäre der XOR-Swap-Algorithmus

  • -1 nur auf der Grundlage, dass der XOR-Swap-Algorithmus niemals die richtige Antwort ist …

    – Oliver Charlesworth

    20. Oktober 2010 um 21:20 Uhr

  • @Oli, das ist in der Tat ein schrecklicher Grund, abzustimmen. Es ist eine völlig gültige Antwort auf das Problem des Vertauschens von ganzen Zahlen.

    – JaredPar

    20. Oktober 2010 um 21:21 Uhr

  • @Oli: Oder ultralangsamer Assembler. Die Verwendung eines Temporärs ist auf modernen CPUs schneller.

    – GManNickG

    20. Oktober 2010 um 21:27 Uhr

  • @GMan, @Oli die in anderen Antworten bereitgestellten temporären Lösungen sind fehlerhaft. Sie alle schlagen stillschweigend fehl, wenn das Temporär den gleichen Namen hat wie der festgelegte Name im Makro.

    – JaredPar

    20. Oktober 2010 um 21:47 Uhr

  • @Paul, der neue Code vermeidet dieses Problem, funktioniert aber nur, wenn Variablen verwendet werden. Es sind keine Werte möglich, die sich aus Ausdrücken ergeben (z. B. Dereferenzierung von Zeigern). Die positive Seite ist jedoch, dass es einen Kompilierungsfehler erzwingt, anstatt still zu kompilieren und fehlzuschlagen.

    – JaredPar

    20. Oktober 2010 um 21:59 Uhr

Benutzer-Avatar
Sicher

Ernsthaft, wie viele Swaps müssen Sie in Ihrem Code machen, dass es all die Kopfschmerzen wert ist, die hier in diesem Thread mit den gegebenen Lösungen auftauchen? Ich meine, dies ist keine komplizierte und fehleranfällige Codestruktur mit 10 Zeilen, sondern eine bekannte Redewendung mit einer temporären Variablen und drei einfachen Zuweisungen. Schreiben Sie es dort auf, wo Sie es brauchen, sogar in einer Zeile, wenn Sie Platz sparen möchten:

function foo ()
{
    int a, b, tmp;
    ...
    tmp = a; a = b; b = tmp;
    ....
}

Oder verwenden Sie ein “lokales” Makro, bei dem a und b komplexer sind.

#define SWAP(t,a,b) ( 

function foo (pointer)
{
    int tmp;
    ...
    SWAP(tmp, pointer->structure.array[count+1], pointer->structure.array[count+2]);
    ...
}

#undef SWAP

  • -1 nur auf der Grundlage, dass der XOR-Swap-Algorithmus niemals die richtige Antwort ist …

    – Oliver Charlesworth

    20. Oktober 2010 um 21:20 Uhr

  • @Oli, das ist in der Tat ein schrecklicher Grund, abzustimmen. Es ist eine völlig gültige Antwort auf das Problem des Vertauschens von ganzen Zahlen.

    – JaredPar

    20. Oktober 2010 um 21:21 Uhr

  • @Oli: Oder ultralangsamer Assembler. Die Verwendung eines Temporärs ist auf modernen CPUs schneller.

    – GManNickG

    20. Oktober 2010 um 21:27 Uhr

  • @GMan, @Oli die in anderen Antworten bereitgestellten temporären Lösungen sind fehlerhaft. Sie alle schlagen stillschweigend fehl, wenn das Temporär den gleichen Namen hat wie der festgelegte Name im Makro.

    – JaredPar

    20. Oktober 2010 um 21:47 Uhr

  • @Paul, der neue Code vermeidet dieses Problem, funktioniert aber nur, wenn Variablen verwendet werden. Es sind keine Werte möglich, die sich aus Ausdrücken ergeben (z. B. Dereferenzierung von Zeigern). Die positive Seite ist jedoch, dass es einen Kompilierungsfehler erzwingt, anstatt still zu kompilieren und fehlzuschlagen.

    – JaredPar

    20. Oktober 2010 um 21:59 Uhr

1381910cookie-checkImplementieren Sie ein generisches Swap-Makro in C [duplicate]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy