Was ist Ihr Lieblings-C-Programmiertrick? [geschlossen]

134

Zum Beispiel bin ich kürzlich im Linux-Kernel darauf gestoßen:

/ * Erzwinge einen Kompilierungsfehler, wenn die Bedingung erfüllt ist * /
#define BUILD_BUG_ON (Bedingung) ((void) sizeof (char [1 - 2 * !! (Bedingung)]))

Wenn Sie also in Ihrem Code eine Struktur haben, die beispielsweise ein Vielfaches von 8 Bytes groß sein muss, möglicherweise aufgrund einiger Hardwareeinschränkungen, können Sie Folgendes tun:

BUILD_BUG_ON ((sizeof (struct mystruct)% 8)! = 0);

und es wird nicht kompiliert, es sei denn, die Größe von struct mystruct ist ein Vielfaches von 8, und wenn es ein Vielfaches von 8 ist, wird überhaupt kein Laufzeitcode generiert.

Ein weiterer Trick, den ich kenne, stammt aus dem Buch "Graphics Gems", mit dem eine einzelne Header-Datei Variablen in einem Modul deklarieren und initialisieren kann, während sie in anderen Modulen, die dieses Modul verwenden, lediglich als extern deklariert werden.

#ifdef DEFINE_MYHEADER_GLOBALS
# GLOBAL definieren
#define INIT (x, y) (x) = (y)
#sonst
#define GLOBAL extern
#define INIT (x, y)
#endif

GLOBAL int INIT (x, 0);
GLOBAL int somefunc (int a, int b);

Damit macht der Code, der x und somefunc definiert, Folgendes:

#define DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

während Code, der lediglich x und somefunc () verwendet, Folgendes tut:

#include "the_above_header_file.h"

Sie erhalten also eine Header-Datei, die sowohl Instanzen von Globals als auch Funktionsprototypen deklariert, wo sie benötigt werden, sowie die entsprechenden externen Deklarationen.

Also, was sind deine Lieblings-C-Programmier-Tricks in dieser Richtung?

smcameron
quelle
9
Dies scheint eher wie C-Präprozessor-Tricks.
jmucchiello
Über BUILD_BUG_ONMakro, was noch falsch mit der Verwendung von #errorinnen und #if?
Ricardo

Antworten:

80

C99 bietet einige wirklich coole Sachen mit anonymen Arrays:

Sinnlose Variablen entfernen

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

wird

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

Übergeben einer variablen Anzahl von Argumenten

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

Statische verknüpfte Listen

int main() {
    struct llist { int a; struct llist* next;};
    #define cons(x,y) (struct llist[]){{x,y}}
    struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL))));
    struct llist *p = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

Ich bin mir sicher, dass ich an viele andere coole Techniken nicht gedacht habe.

Evan Teran
quelle
2
Ich glaube, Ihr erstes Beispiel kann auch so geschrieben werden &(int){1}, wenn Sie etwas klarer machen möchten, was Ihre Absicht hier ist.
Lily Ballard
67

Beim Lesen des Quake 2-Quellcodes habe ich mir Folgendes ausgedacht:

double normals[][] = {
  #include "normals.txt"
};

(Mehr oder weniger habe ich den Code nicht zur Hand, um ihn jetzt zu überprüfen).

Seitdem öffnete sich vor meinen Augen eine neue Welt des kreativen Einsatzes des Präprozessors. Ich füge nicht mehr nur Header hinzu, sondern ab und zu ganze Codestücke (dies verbessert die Wiederverwendbarkeit erheblich) :-p

Danke John Carmack! xD

fortran
quelle
13
Sie können in einem Optimierungsthread nicht Carmack sagen, ohne das schnelle inverse Quadrat zu erwähnen, das sich in der Bebenquelle befand. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989
Woher hat er überhaupt 0x5f3759df?
RSH1
2
@RoryHarvey: Nach allem, was ich beim Nachschlagen finden konnte, scheint es rein empirisch zu sein. Einige Studien (ich erinnere mich nicht, wo ich sie gesehen habe) haben gezeigt, dass es nahezu optimal, aber nicht vollständig optimal war. Ebenso scheint es, dass für 64 Bit der Wert entdeckt wurde, anstatt zu berechnen.
Matthieu M.
50

Ich verwende gerne = {0};Strukturen, um Strukturen zu initialisieren, ohne memset aufrufen zu müssen.

struct something X = {0};

Dadurch werden alle Elemente der Struktur (oder des Arrays) auf Null initialisiert (jedoch keine Füllbytes - verwenden Sie memset, wenn Sie diese ebenfalls auf Null setzen müssen).

Sie sollten sich jedoch bewusst sein, dass es bei großen, dynamisch zugewiesenen Strukturen einige Probleme gibt .

John Carter
quelle
Übrigens nicht für globale Variablen erforderlich.
Strager
5
Wird für statische Variablen nicht benötigt . Globale Variablen können auf Null gesetzt werden, dies ist jedoch keine Voraussetzung.
Jamie
4
Ich erweitere dies manchmal auf: const struct something zero_something = { 0 };und dann kann ich eine Variable im struct something X = zero_something;laufenden Betrieb mit oder während einer Routine zurücksetzen, die ich mit 'X = zero_something;' verwenden kann. Der einzig mögliche Einwand besteht darin, dass Daten von irgendwoher gelesen werden müssen. Heutzutage ist ein 'memset ()' vielleicht schneller - aber ich mag die Klarheit der Zuweisung, und es ist auch möglich, im Initialisierer (und memset () Werte ungleich Null zu verwenden, gefolgt von Optimierungen an einzelnen Mitgliedern kann langsamer sein als eine einfache Kopie).
Jonathan Leffler
45

Wenn wir über C-Tricks sprechen, muss mein Favorit Duffs Gerät zum Abrollen der Schleife sein! Ich warte nur auf die richtige Gelegenheit, damit ich sie tatsächlich im Zorn nutzen kann ...

Jackson
quelle
4
Ich habe es einmal verwendet, um einen messbaren Leistungsgewinn zu erzielen, aber heutzutage ist es auf vielen Hardwarekomponenten nicht nützlich. Immer Profil!
Dan Olson
6
Ja, die Art von Leuten, die den Kontext nicht verstehen, in dem Duffs Gerät erstellt wurde: "Lesbarkeit des Codes" ist nutzlos, wenn der Code nicht schnell genug ist, um zu funktionieren. Wahrscheinlich musste keiner der Leute, die Sie herabgestimmt haben, jemals für harte Echtzeit programmieren.
Rob K
1
+1, ich musste tatsächlich ein paar Mal Duffs Gerät benutzen. Das erste Mal war eine Schleife, die im Grunde nur Sachen kopierte und unterwegs eine kleine Transformation durchführte. Es war viel, viel schneller als ein einfaches memcpy () in dieser Architektur.
Makis
3
Der Ärger wird von Ihren Kollegen und Nachfolgern ausgehen, die Ihren Code nach Ihnen pflegen müssen.
Jonathan Leffler
1
Wie gesagt, ich warte immer noch auf die richtige Gelegenheit - aber noch hat mich niemand genug geärgert. Ich schreibe C seit ungefähr 25 Jahren. Ich glaube, ich bin in den frühen 90ern zum ersten Mal auf Duffs Gerät gestoßen und musste es noch nicht verwenden. Wie andere kommentiert haben, ist diese Art von Trick immer weniger nützlich, da Compiler bei dieser Art der Optimierung besser werden.
Jackson
42

Verwenden __FILE__und __LINE__zum Debuggen

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);
Pierre
quelle
6
Auf einigen Compilern erhalten Sie auch FUNCTION .
JBRWilkinson
11
__FUNCTION__ist nur ein Alias ​​für __func__und __func__ist in c99. Sehr praktisch. __PRETTY_FUNCTION__in C (GCC) ist nur ein weiterer Alias ​​für __func__, aber in C ++ erhalten Sie die vollständige Funktionssignatur.
sklnd
DATEI zeigt den vollständigen Pfad des Dateinamens, also verwende ich den Basisnamen ( DATEI )
Jeegar Patel
31

In C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};
Jasper Bekkers
quelle
28

Einmal haben ein Freund von mir und ich die Rückkehr neu definiert, um einen kniffligen Fehler bei der Stapelkorruption zu finden.

Etwas wie:

#define return DoSomeStackCheckStuff, return
Andrew Barrett
quelle
4
Hoffentlich wurde das im Funktionskörper definiert und am Ende nicht definiert!
Strager
Das gefällt mir nicht besonders gut - das erste, was mir in den Sinn kommt, ist, dass DoSomeStackCheckStuff aufgrund eines Fehlers den Speicher durcheinander bringt und wer den Code liest, ist sich der Neudefinition der Rückkehr nicht bewusst und fragt sich, was zum Teufel los ist.
Gilligan
8
@strager Aber das würde es im Grunde unbrauchbar machen. Der springende Punkt ist, jedem Funktionsaufruf eine Ablaufverfolgung hinzuzufügen . Andernfalls würden Sie nur DoSomeStackCheckStuffdie Funktionen aufrufen , die Sie verfolgen möchten .
Ahnungslos
1
@ Gilligan Ich glaube nicht, dass dies die Art von Dingen ist, die Sie die ganze Zeit aktiviert lassen. Es scheint ziemlich praktisch für One-Shot-Debugging-Arbeiten zu sein.
Sunetos
funktioniert das wirklich :) Ich hätte geschrieben #define return if((DoSomeStackCheckStuff) && 0) ; else return... genauso verrückt, denke ich!
Paolo Bonzini
22

Ich mag den "Struktur-Hack" für ein dynamisch großes Objekt. Diese Seite erklärt es auch ziemlich gut (obwohl sie sich auf die C99-Version beziehen, in der Sie "str []" als letztes Mitglied einer Struktur schreiben können). Sie könnten einen String "Objekt" wie folgt machen:

struct X {
    int len;
    char str[1];
};

int n = strlen("hello world");
struct X *string = malloc(sizeof(struct X) + n);
strcpy(string->str, "hello world");
string->len = n;

Hier haben wir eine Struktur vom Typ X auf dem Heap zugewiesen, die die Größe eines int (für len) plus der Länge von "Hallo Welt" plus 1 hat (da str 1 in der Größe von (X) enthalten ist.

Dies ist im Allgemeinen nützlich, wenn Sie einen "Header" direkt vor einigen Daten variabler Länge im selben Block haben möchten.

Evan Teran
quelle
Ich persönlich finde es einfacher, nur malloc () und realloc () selbst zu verwenden und strlen () zu verwenden, wann immer ich die Länge finden muss, aber wenn Sie ein Programm benötigen, das die Länge der Zeichenfolge nie kennt und wahrscheinlich viele finden muss Mal ist dies wahrscheinlich die bessere Straße.
Chris Lutz
4
"... die C99-Version, in der Sie" str [] "schreiben können." Ich habe in einem solchen Kontext Arrays mit der Größe Null gesehen, wie str [0]. ziemlich oft. Ich denke es ist C99. Ich weiß, dass ältere Compiler sich über Arrays mit der Größe Null beschweren.
Smcameron
3
Ich mag dieses auch, aber Sie sollten etwas wie malloc (offsetof (X, str) + numbytes) verwenden, da sonst aufgrund von Problemen beim Auffüllen und Ausrichten etwas schief geht. ZB könnte sizeof (struct X) 8 sein, nicht 5.
Fozi
3
@Fozi: Ich glaube eigentlich nicht, dass das ein Problem wäre. Da diese Version str[1](nicht str[]) das 1-Byte von str hat, ist in der enthalten sizeof(struct X). Dies schließt jegliche Polsterung zwischen lenund ein str.
Evan Teran
2
@ Rusky: Wie würde sich das negativ auswirken? Angenommen, es gibt danach "Polsterung" str. OK, wenn ich zuweise, sizeof(struct X) + 10dann macht dies streffektiv 10 - sizeof(int)(oder mehr, da wir sagten, dass es Polsterung gibt) groß. Dies überlagert str und jede Polsterung danach. Der einzige Weg, auf dem es einen Unterschied geben würde, ist, wenn es ein Mitglied gäbe, nach strdem das Ganze sowieso kaputt geht, müssen flexible Mitglieder die letzten sein. Eine Polsterung am Ende führt möglicherweise nur dazu, dass zu viel zugewiesen wird. Bitte geben Sie ein konkretes Beispiel dafür, wie es tatsächlich schief gehen könnte.
Evan Teran
17

Objektorientierter Code mit C durch Emulieren von Klassen.

Erstellen Sie einfach eine Struktur und eine Reihe von Funktionen, die als ersten Parameter einen Zeiger auf diese Struktur verwenden.

Brian R. Bondy
quelle
2
Gibt es noch etwas, das C ++ in C übersetzt, wie es Cfront gewohnt ist?
MarkJ
11
Dies ist kaum Objektorientierung. Für OO mit Vererbung müssen Sie Ihrer Objektstruktur eine Art virtuelle Funktionstabelle hinzufügen, die von "Unterklassen" überladen werden kann. Zu diesem Zweck gibt es viele halbgebackene Frameworks im "C with classes" -Stil, aber ich empfehle, sich davon fernzuhalten.
exDM69
Es musste gesagt werden. +1 dafür.
Amit S
3
@ exDM69, Objektorientierung ist sowohl eine Art, über ein Problem nachzudenken, als auch ein Codierungsparadigma. Sie können es erfolgreich ohne Vererbung tun. Ich habe dies bei einigen Projekten getan, bevor ich mich voll und ganz mit C ++ befasst habe.
Mark Ransom
16

Anstatt

printf("counter=%d\n",counter);

Verwenden

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);
Konst
quelle
14

Verwenden eines dummen Makrotricks, um die Pflege von Datensatzdefinitionen zu vereinfachen.

#define COLUMNS(S,E) [(E) - (S) + 1]

typedef struct
{
    char studentNumber COLUMNS( 1,  9);
    char firstName     COLUMNS(10, 30);
    char lastName      COLUMNS(31, 51);

} StudentRecord;
EvilTeach
quelle
11

Zum Erstellen einer Variablen, die in allen Modulen außer der schreibgeschützten Variablen schreibgeschützt ist:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere
Steve Melnikoff
quelle
Das fühlt sich gefährlich an. Dies sind Deklarationen und Definitionen, die nicht übereinstimmen. Während des Kompilierens Source2.ckann der Compiler davon ausgehen, dass sich MyVardies auch bei einem Funktionsaufruf an nicht ändert Source1.c. (Beachten Sie, dass dies als tatsächliche const-Variable von einem Zeiger auf const abweicht. Im letzteren Fall kann das Objekt, auf das verwiesen wird, noch über einen anderen Zeiger geändert werden.)
jilles
1
Dies erzeugt keine Variable, die nur in einigen Kompilierungseinheiten schreibgeschützt ist. Dies führt zu undefiniertem Verhalten (siehe S. 6.2.7.2 von ISO 9899 und auch S. 6.7.3.5).
Ales Hakl
8

Bitverschiebungen werden nur bis zu einem Verschiebungsbetrag von 31 (auf einer 32-Bit-Ganzzahl) definiert.

Was machen Sie, wenn Sie eine berechnete Schicht haben möchten, die auch mit höheren Schichtwerten arbeiten muss? So macht es der Theora-Videocodec:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

Oder viel lesbarer:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

  return (a >> halfshift) >> otherhalf; 
}

Das Ausführen der Aufgabe auf die oben gezeigte Weise ist viel schneller als die Verwendung eines Zweigs wie diesem:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}
Nils Pipenbrinck
quelle
... und gcc inlines es tatsächlich :) +1
Tim Post
2
Auf meinem Computer entfernt gcc-4.3.2 den Zweig im zweiten mithilfe einer cmov-Anweisung (bedingte Verschiebung)
Adam Rosenfield
3
"Viel schneller als die Verwendung eines Zweigs": Der Unterschied besteht darin, dass der Zweig für alle Werte von korrekt ist v, während der halfshiftTrick den zulässigen Bereich bei einer 32-Bit-Architektur nur auf 63 und bei einer 64-Bit-Architektur auf 127 verdoppelt.
Pascal Cuoq
8

Deklarieren von Zeigerarrays auf Funktionen zum Implementieren von Finite-State-Maschinen.

int (* fsm[])(void) = { ... }

Der erfreulichste Vorteil ist, dass es einfach ist, jeden Stimulus / Zustand zu zwingen, alle Codepfade zu überprüfen.

In einem eingebetteten System ordne ich häufig einen ISR zu, um auf eine solche Tabelle zu verweisen, und revektoriere sie nach Bedarf (außerhalb des ISR).

Jamie
quelle
Eine Technik, die mir dabei gefällt, ist, wenn Sie eine Funktion haben, die initialisiert werden muss, initialisieren Sie den Zeiger mit einem Aufruf der Initialisierungsroutine. Wenn dies ausgeführt wird, ersetzen Sie als letztes den Zeiger durch einen Zeiger auf die eigentliche Funktion und rufen diese Funktion dann auf. Auf diese Weise wird der Initialisierer beim ersten Aufruf der Funktion automatisch aufgerufen, und die eigentliche Funktion wird jedes Mal aufgerufen.
TMN
7

Ein weiterer netter "Trick" vor dem Prozessor besteht darin, das Zeichen "#" zum Drucken von Debugging-Ausdrücken zu verwenden. Beispielsweise:

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

Bearbeiten: Der folgende Code funktioniert nur unter C ++. Vielen Dank an smcameron und Evan Teran.

Ja, die Kompilierungszeit ist immer großartig. Es kann auch geschrieben werden als:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]
Gilad Naor
quelle
Das COMPILE_ASSERT-Makro kann jedoch nicht zweimal verwendet werden, da es den Namespace mit einem typedef verschmutzt und die zweite Verwendung Folgendes erhält: Fehler: Neudefinition von typedef '__compile_time_assert'
smcameron
Hast du das tatsächlich versucht? Sie können "typedef foo;" so oft du willst. So machen Sie Vorerklärungen. Ich habe es seit 2,5 Jahren auf mehreren Compilern verwendet, sowohl auf gcc, VC als auch auf einem Compiler für eine eingebettete Umgebung, und bin nie auf Schwierigkeiten gestoßen.
Gilad Naor
Ich hasse den C-Präprozessor ... :(
hasen
1
Ja, ich habe es versucht. Ich habe die Fehlermeldung vom Compiler, die gcc war, ausgeschnitten und eingefügt.
Smcameron
1
@ Gilad: In C ++ ist es legal, redundante Typedefs zu haben, aber nicht in c.
Evan Teran
6

Ich würde es nicht wirklich als Lieblingstrick bezeichnen, da ich es nie benutzt habe, aber die Erwähnung von Duffs Gerät erinnerte mich an diesen Artikel über die Implementierung von Coroutines in C. Es gibt mir immer ein Kichern, aber ich bin mir sicher, dass es das könnte irgendwann nützlich sein.

Dan Olson
quelle
Ich habe diese Technik tatsächlich in der Praxis verwendet, um den Code, der eine Folge von abhängigen asynchronen E / A steuert, vage lesbar zu machen. Der Hauptunterschied besteht darin, dass ich den Coroutine-Status nicht in einer staticVariablen speichere , sondern stattdessen eine Struktur dynamisch zuordne und einen Zeiger darauf an die Coroutine-Funktion übergebe. Eine Reihe von Makros machen dies schmackhafter. Es ist nicht schön, aber besser als die Async / Callback-Version, die überall herumspringt. Ich würde grüne Threads (via swapcontext()on * nixes) verwenden, wenn ich könnte.
pmdj
6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

Die Weile (0); hat keine Auswirkung auf das Programm, aber der Compiler gibt eine Warnung über "Dies macht nichts" aus. Dies reicht aus, um mich dazu zu bringen, auf die fehlerhafte Zeile zu schauen und dann den wahren Grund zu sehen, warum ich darauf aufmerksam machen wollte.

gbarry
quelle
9
Könntest du nicht stattdessen #warning verwenden?
Stefano Borini
Anscheinend konnte ich. Es ist nicht ganz Standard, aber es hat in den Compilern funktioniert, die ich benutze. Interessanterweise übersetzte der eingebettete Compiler ein #define, während gcc dies nicht tat.
Gbarry
6

Ich bin ein Fan von XOR-Hacks:

2 Zeiger ohne dritten temporären Zeiger tauschen:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

Oder ich mag die xor verknüpfte Liste mit nur einem Zeiger wirklich. (http://en.wikipedia.org/wiki/XOR_linked_list)

Jeder Knoten in der verknüpften Liste ist das X oder des vorherigen Knotens und des nächsten Knotens. Um vorwärts zu fahren, wird die Adresse der Knoten auf folgende Weise gefunden:

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

etc.

oder rückwärts fahren:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

etc.

Obwohl es nicht besonders nützlich ist (Sie können nicht von einem beliebigen Knoten aus durchqueren), finde ich es sehr cool.

hamiltop
quelle
5

Dieser stammt aus dem Buch 'Genug Seil, um sich in den Fuß zu schießen':

In der Kopfzeile deklarieren

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

Platzieren Sie in Ihrem Code Testanweisungen, z.

D(printf("Test statement\n"));

Das do / while hilft, falls der Inhalt des Makros auf mehrere Anweisungen erweitert wird.

Die Anweisung wird nur gedruckt, wenn das Flag '-D RELEASE' für den Compiler nicht verwendet wird.

Sie können dann z. Übergebe die Flagge an dein Makefile usw.

Ich bin mir nicht sicher, wie das in Windows funktioniert, aber in * nix funktioniert es gut

Simon Walker
quelle
Möglicherweise möchten Sie D (x) auf {} erweitern, wenn RELEASE definiert ist, damit es mit if-Anweisungen gut funktioniert. Andernfalls "wenn (a) D (x);" wird nur auf "if (a)" erweitert, wenn Sie RELEASE definiert haben. Das wird Ihnen einige nette Fehler in der RELEASE-Version geben
MarkJ
3
@ MarkJ: NEIN. So wie es ist, "wenn (a) D (x);" erweitert sich zu "if (a);" Das ist vollkommen in Ordnung. Wenn Sie D (x) auf {} erweitern ließen, dann "if (a) if (b) D (x); else foo ();" würde UNKORREKT auf "if (a) if (b) {}; else foo ();" erweitern, wodurch das "else foo ()" mit dem zweiten if anstelle des ersten if übereinstimmt.
Adam Rosenfield
Um ehrlich zu sein, benutze ich dieses Makro meistens zum Testen von Druckanweisungen, oder wenn ich eine bedingte Anweisung hätte, würde ich alles einschließen, z. D (wenn (a) foo (););
Simon Walker
1
@AdamRosenfield: Die Verwendung von #define D(x) do { } while(0)stattdessen behandelt diesen Fall (und kann auf den Zweig angewendet werden, der xaus
Gründen der
3

Rusty produzierte tatsächlich eine ganze Reihe von Build-Bedingungen in ccan erstellt. Schauen Sie sich das Build- Assert- Modul an:

#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

Es gibt viele andere hilfreiche Makros im eigentlichen Header, die sich leicht einfügen lassen.

Ich versuche mit aller Kraft, dem Zug der dunklen Seite (und dem Missbrauch durch Präprozessoren) zu widerstehen, indem ich mich hauptsächlich an Inline-Funktionen halte, aber ich mag clevere, nützliche Makros wie die von Ihnen beschriebenen.

Tim Post
quelle
Ja, ich bin kürzlich auf ccan gestoßen und habe überlegt, Code beizusteuern, habe mich aber noch nicht um den "ccan-Weg" gekümmert. Vielen Dank für den Link, mehr Motivation, sich mit ccan zu beschäftigen, was ich wirklich hoffe, etwas Traktion zu bekommen.
Smcameron
Nun, ich würde mich nicht allzu sehr um den "Ccan-Weg" kümmern, bis er etablierter ist ... im Moment wird Ccan-Lint als GSOC-Projekt vorgeschlagen. Es ist eine kleine und ziemlich freundliche Gruppe ... und ein großartiger Ort, um Schnipsel zu werfen :)
Tim Post
Übrigens ist mir aufgefallen, dass Rustys BuILD_ASSERT genau wie das Makro aus dem Linux-Kernel ist (nicht überraschend), aber eines der "Nots" (oder Pony oder!) Fehlt, und ich denke, dass meine Beispielverwendung des von mir geposteten Makros ist nicht richtig. Sollte gewesen sein: "BUILD_BUG_ON ((sizeof (struct mystruct)% 8))"
smcameron
3

Zwei gute Quellenbücher für diese Art von Dingen sind Die Praxis des Programmierens und Schreibens von festem Code . Einer von ihnen (ich weiß nicht mehr, welcher) sagt: Bevorzugen Sie enum gegenüber #define, wo Sie können, da enum vom Compiler überprüft wird.

Yuval F.
quelle
1
AFAIK, in C89 / 90 gibt es KEINE Typprüfung für Aufzählungen. Aufzählungen sind einfach irgendwie bequemer #defines.
Cschol
Unten auf Seite 39, 2. ED K & R. Es besteht zumindest die Möglichkeit zur Überprüfung.
Jonathan Watmough
3

Nicht spezifisch für C, aber ich habe den XOR-Operator immer gemocht. Eine coole Sache, die es tun kann, ist "Tauschen ohne temporären Wert":

int a = 1;
int b = 2;

printf("a = %d, b = %d\n", a, b);

a ^= b;
b ^= a;
a ^= b;

printf("a = %d, b = %d\n", a, b);

Ergebnis:

a = 1, b = 2

a = 2, b = 1

Karl
quelle
a = 1; b = 2; a = a + b; b = ab; a = ab; gibt das gleiche Ergebnis auch
Grambot
Dies wird auch a und b tauschen: a ^ = b ^ = a ^ = b;
Vikhyat
@TheCapn: Der Zusatz kann jedoch überlaufen.
Michael Foukarakis
2

Ich mag das Konzept container_of, zum Beispiel in Listen verwendet zu werden. Grundsätzlich müssen Sie nicht angeben , nextundlast Felder für jede Struktur , die in der Liste sein. Stattdessen hängen Sie den Listenstrukturkopf an die tatsächlich verknüpften Elemente an.

Schauen Sie sich include/linux/list.hBeispiele aus der Praxis an.

Viliam
quelle
1

Ich denke, die Verwendung von Benutzerdatenzeigern ist ziemlich ordentlich. Eine Mode, die heutzutage an Boden verliert. Es ist nicht so sehr eine C-Funktion, aber in C ziemlich einfach zu verwenden.

Epatel
quelle
1
Ich wünschte, ich hätte verstanden, was Sie hier meinten. Könnten Sie mehr erklären? Was ist ein Benutzerdatenzeiger?
Zan Lynx
1
Bitte sehen Sie hier stackoverflow.com/questions/602826/…
Epatel
Es ist in erster Linie für Rückrufe. Es sind einige Daten, die Sie bei jedem Rückruf zurückgeben möchten. Besonders nützlich für die Übergabe eines C ++ - Zeigers an einen Rückruf, damit Sie ein Objekt an ein Ereignis binden können.
Evan Teran
Ah ja. Vielen Dank. Ich benutze das oft, aber ich habe es nie so genannt.
Zan Lynx
1

Ich benutze X-Makros , um den Pre-Compiler Code generieren zu lassen. Sie sind besonders nützlich, um Fehlerwerte und zugehörige Fehlerzeichenfolgen an einer Stelle zu definieren, können jedoch weit darüber hinausgehen.

JayG
quelle
1

Unsere Codebasis hat einen ähnlichen Trick wie

#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

Dies ermöglicht die Verfolgung von Speicherlecks im Debug-Modus. Ich fand das immer cool.

jdizzle
quelle
1

Spaß mit Makros:

#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)

/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}
sanjoyd
quelle
0

Hier ist ein Beispiel, wie Sie C-Code völlig unbewusst machen können, was tatsächlich von HW zum Ausführen der App verwendet wird. Die main.c führt das Setup durch und dann kann die freie Ebene auf jedem Compiler / Arch implementiert werden. Ich denke, es ist ziemlich ordentlich, um C-Code ein wenig zu abstrahieren, so dass es nicht zu spezifisch wird.

Fügen Sie hier ein vollständiges kompilierbares Beispiel hinzu.

/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x\n",my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init\n");
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x\n",send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}
eaanon01
quelle
4
Möchtest du etwas ausarbeiten, vielleicht eine praktische Verwendung erklären?
Leonardo Herrera
Als Beispiel, wenn ich ein Testprogramm mit einer HW-Schnittstelle schreiben muss, die am Ende Interrupts generiert. Dann kann dieses Modul so eingerichtet werden, dass eine Funktion außerhalb des normalen Bereichs als Signal- / Interrupt-Handler ausgeführt wird.
eaanon01
0
if(---------)  
printf("hello");  
else   
printf("hi");

Füllen Sie die Lücken aus, damit weder Hallo noch Hallo in der Ausgabe erscheinen.
ans:fclose(stdout)

justgo
quelle
Sie können Code mit der {}Symbolleistenschaltfläche formatieren (ich habe es für Sie erledigt). Die Schaltfläche "Anführungszeichen" behält keine Leerzeichen bei und wendet keine Syntaxhervorhebung an.
Álvaro González