it-swarm.com.de

Versteckte Funktionen von C

Ich weiß, dass hinter allen C-Compiler-Implementierungen ein Standard steckt, daher sollte es keine versteckten Funktionen geben. Trotzdem bin ich sicher, dass alle C-Entwickler versteckte/geheime Tricks haben, die sie ständig anwenden.

141
bernardn

Funktionszeiger. Sie können eine Tabelle mit Funktionszeigern verwenden, um beispielsweise schnelle FORTH- (Indirect Threaded Code Interpreter) oder Bytecode-Dispatcher zu implementieren oder OO-ähnliche virtuelle Methoden zu simulieren.

Dann gibt es in der Standardbibliothek versteckte Juwelen wie qsort (), bsearch (), strpbrk (), strcspn () [die beiden letzteren sind nützlich, um einen strtok () - Ersatz zu implementieren].

Ein Fehler von C ist, dass der vorzeichenbehaftete arithmetische Überlauf undefiniertes Verhalten (UB) ist. Wenn Sie also einen Ausdruck wie x + y sehen, der beide signiert ist, kann er möglicherweise überlaufen und UB verursachen.

62
zvrba

Eher ein Trick des GCC-Compilers, aber Sie können dem Compiler Hinweise zur Verzweigung geben (im Linux-Kernel üblich).

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

siehe: http://kerneltrap.org/node/4705

Was ich daran mag, ist, dass es auch einigen Funktionen Ausdruck verleiht.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}
116
tonylo
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Dies ist ein optionales Element im Standard, das jedoch ausgeblendet sein muss, da die Benutzer es ständig neu definieren. Eine Codebasis, an der ich gearbeitet habe (und bis jetzt immer noch arbeite), verfügt über mehrere Neudefinitionen, alle mit unterschiedlichen Bezeichnern. Meistens ist es mit Präprozessor-Makros:

#define INT16 short
#define INT32  long

Und so weiter. Es bringt mich dazu, mir die Haare auszureißen. Verwenden Sie einfach die Standard-Integer-Typedefs!

77
Ben Collins

Der Komma-Operator ist nicht weit verbreitet. Es kann sicherlich missbraucht werden, aber es kann auch sehr nützlich sein. Diese Verwendung ist die häufigste:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

Sie können diesen Operator jedoch überall verwenden. Beobachten:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

Jede Anweisung wird ausgewertet, aber der Wert des Ausdrucks entspricht dem der zuletzt ausgewerteten Anweisung.

73
Ben Collins

Struktur auf Null initialisieren

struct mystruct a = {0};

dadurch werden alle Strukturelemente auf Null gesetzt.

63
mike511

Mehrzeichenkonstanten:

int x = 'ABCD';

Dies setzt x auf 0x41424344 (oder 0x44434241, je nach Architektur).

EDIT: Diese Technik ist nicht portierbar, insbesondere wenn Sie das int serialisieren. Es kann jedoch äußerst nützlich sein, selbstdokumentierende Aufzählungen zu erstellen. z.B.

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

Dies macht es viel einfacher, wenn Sie einen Raw Memory Dump betrachten und den Wert einer Enumeration bestimmen müssen, ohne ihn nachschlagen zu müssen.

52
Ferruccio

Ich habe nie Bitfelder verwendet, aber sie klingen cool für ultra-Low-Level-Sachen.

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

Dies bedeutet, dass sizeof(cat) so klein sein kann wie sizeof(char).


Eingefügte Kommentare von Aaron und Leppie , danke Jungs.

44
Motti

Interlacing-Strukturen wie Duffs Gerät :

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
37
ComSubVie

C hat einen Standard, aber nicht alle C-Compiler sind vollständig kompatibel (ich habe noch keinen vollständig kompatiblen C99-Compiler gesehen!).

Die von mir bevorzugten Tricks sind jedoch nicht offensichtliche und plattformübergreifende Tricks, die sich auf die C-Semantik stützen. In der Regel handelt es sich um Makros oder Bit-Arithmetik.

Beispiel: Vertauschen von zwei vorzeichenlosen Ganzzahlen ohne Verwendung einer temporären Variablen:

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

oder "Erweitern von C", um endliche Zustandsmaschinen darzustellen, wie:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

dies kann mit den folgenden Makros erreicht werden:

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

Im Allgemeinen mag ich die Tricks nicht, die clever sind, aber die das Lesen des Codes unnötig erschweren (als Swap-Beispiel), und ich mag diejenigen, die den Code klarer machen und die Absicht direkt vermitteln (wie das FSM-Beispiel). .

37
Remo.D

Ich bin sehr angetan von bestimmten Initialisierern, die in C99 hinzugefügt wurden (und seit langer Zeit in gcc unterstützt werden):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

Die Array-Initialisierung ist nicht mehr positionsabhängig. Wenn Sie die Werte von FOO oder BAR ändern, entspricht die Array-Initialisierung automatisch ihrem neuen Wert.

33
DGentry

C99 hat einige großartige Strukturinitialisierungen in beliebiger Reihenfolge.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}
</ code>
28
Jason

anonyme Strukturen und Arrays sind meine Lieblingsstrukturen. (Siehe http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

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

oder

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

es kann sogar verwendet werden, um verknüpfte Listen zu instanziieren ...

27
PypeBros

das (versteckte) Feature, das mich "schockierte", als ich es zum ersten Mal sah, handelt von printf. Mit dieser Funktion können Sie Variablen zum Formatieren von Formatbezeichnern selbst verwenden. Suchen Sie nach dem Code, Sie werden besser sehen:

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

das Zeichen * erzielt diesen Effekt.

24
kolistivra

gcc hat eine Reihe von Erweiterungen für die C-Sprache, die ich mag, die zu finden sind hier . Einige meiner Favoriten sind Funktionsattribute . Ein äußerst nützliches Beispiel ist das Formatattribut. Dies kann verwendet werden, wenn Sie eine benutzerdefinierte Funktion definieren, die eine Zeichenfolge im printf-Format verwendet. Wenn Sie dieses Funktionsattribut aktivieren, überprüft gcc Ihre Argumente, um sicherzustellen, dass Ihre Formatzeichenfolge und Argumente übereinstimmen, und generiert gegebenenfalls Warnungen oder Fehler.

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));
24
Russell Bryant

Nun ... Ich denke, dass eine der Stärken der C-Sprache ihre Portabilität und Standardität ist. Wenn ich also in der Implementierung, die ich derzeit verwende, einen "versteckten Trick" finde, versuche ich, ihn nicht zu verwenden, weil ich versuche, meinen zu behalten C-Code als Standard und portabel wie möglich.

24

Assertionen zur Kompilierungszeit, wie hier bereits besprochen .

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);
19
philant

Konstante String-Verkettung

Ich war ziemlich überrascht, als ich es nicht bereits in den Antworten sah, da alle mir bekannten Compiler es unterstützen, aber viele Programmierer scheinen es zu ignorieren. Manchmal ist es sehr praktisch und nicht nur beim Schreiben von Makros.

Anwendungsfall, den ich in meinem aktuellen Code habe: Ich habe ein #define PATH "/some/path/" in einer Konfigurationsdatei (eigentlich wird es durch das Makefile festgelegt). Jetzt möchte ich den vollständigen Pfad einschließlich der Dateinamen zum Öffnen von Ressourcen erstellen. Es geht nur um:

fd = open(PATH "/file", flags);

Anstelle der schrecklichen, aber sehr häufig:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

Beachten Sie, dass die übliche schreckliche Lösung ist:

  • dreimal so lang
  • viel weniger leicht zu lesen
  • viel langsamer
  • weniger leistungsfähig, wenn eine willkürliche Begrenzung der Puffergröße festgelegt ist (aber Sie müssten noch längeren Code verwenden, um dies ohne konstante Verkettung der Zeichenfolgen zu vermeiden).
  • verwenden Sie mehr Stapelplatz
16
kriss

Nun, ich habe es nie benutzt und bin mir nicht sicher, ob ich es jemals jemandem empfehlen würde, aber ich glaube, diese Frage wäre unvollständig, ohne Simon Tathams Co-Routine-Trick zu erwähnen =

15
Mark Baker

Beim Initialisieren von Arrays oder Enums können Sie nach dem letzten Eintrag in der Initialisierungsliste ein Komma setzen. z.B:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

Dies wurde getan, damit Sie sich beim automatischen Generieren von Code keine Gedanken über das Entfernen des letzten Kommas machen müssen.

12
Ferruccio

Strukturelle Zuordnung ist cool. Viele Leute scheinen nicht zu begreifen, dass Strukturen auch Werte sind und zugewiesen werden können. Es ist nicht erforderlich, memcpy() zu verwenden, wenn eine einfache Zuweisung den Trick macht.

Betrachten Sie beispielsweise eine imaginäre 2D-Grafikbibliothek, die möglicherweise einen Typ zur Darstellung einer (ganzzahligen) Bildschirmkoordinate definiert:

typedef struct {
   int x;
   int y;
} Point;

Jetzt machen Sie Dinge, die "falsch" aussehen könnten, wie das Schreiben einer Funktion, die einen aus Funktionsargumenten initialisierten Punkt erzeugt und ihn wie folgt zurückgibt:

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

Dies ist sicher, solange (natürlich) der Rückgabewert durch die Zuweisung von struct durch value kopiert wird:

Point Origin;
Origin = point_new(0, 0);

Auf diese Weise können Sie sehr sauberen und objektorientierten Code schreiben, alles in einfachem Standard C.

12
unwind

Seltsame Vektorindizierung:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */
10
INS

Wenn Sie sscanf verwenden, können Sie mit% n herausfinden, wo Sie weiterlesen sollten:

sscanf ( string, "%d%n", &number, &length );
string += length;

Anscheinend können Sie keine weitere Antwort hinzufügen. Deshalb werde ich hier eine zweite Antwort einfügen. Sie können "&&" und "||" verwenden. als Bedingung:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

Dieser Code gibt Folgendes aus:

Hallo 
 ROFL
9
onemasse

C-Compiler implementieren einen von mehreren Standards. Ein Standard bedeutet jedoch nicht, dass alle Aspekte der Sprache definiert sind. Duffs Gerät ist beispielsweise ein beliebtes "verstecktes" Feature, das so populär geworden ist, dass moderne Compiler über einen speziellen Erkennungscode verfügen, um sicherzustellen, dass Optimierungstechniken den gewünschten Effekt dieses häufig verwendeten Musters nicht beeinträchtigen.

Im Allgemeinen wird davon abgeraten, verborgene Funktionen oder Sprachtricks zu verwenden, wenn Sie auf dem Razor Edge eines oder mehrerer von Ihrem Compiler verwendeter C-Standards ausgeführt werden. Viele solcher Tricks funktionieren nicht von einem Compiler zum anderen, und häufig schlagen solche Funktionen von einer Version einer Compiler-Suite eines bestimmten Herstellers zu einer anderen Version fehl.

Verschiedene Tricks, die C-Code gebrochen haben, sind:

  1. Verlassen Sie sich darauf, wie der Compiler Strukturen im Speicher anordnet.
  2. Annahmen zur Endianität von ganzen Zahlen/Gleitkommazahlen.
  3. Annahmen zur Funktion ABIs.
  4. Annahmen über die Richtung, in der Stapelrahmen wachsen.
  5. Annahmen über die Ausführungsreihenfolge innerhalb von Anweisungen.
  6. Annahmen über die Reihenfolge der Ausführung von Anweisungen in Funktionsargumenten.
  7. Annahmen zur Bitgröße oder Genauigkeit von Short-, Int-, Long-, Float- und Double-Typen.

Andere Probleme und Probleme, die auftreten, wenn Programmierer Annahmen über Ausführungsmodelle treffen, die in den meisten C-Standards als "compilerabhängiges" Verhalten angegeben sind.

9
Kevin S.

Gcc (c) bietet einige unterhaltsame Funktionen, die Sie aktivieren können, z. B. verschachtelte Funktionsdeklarationen und die a?: B-Form des?: -Operators, der a zurückgibt, wenn a nicht falsch ist.

8
Alex Brown

Annahmenüberprüfung zur Kompilierungszeit mithilfe von Aufzählungen: Ein blödes Beispiel, kann aber für Bibliotheken mit konfigurierbaren Konstanten zur Kompilierungszeit sehr nützlich sein.

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};
8
S.C. Madsen

Ich habe kürzlich 0 Bitfelder entdeckt.

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

was ein Layout von geben wird

000aaabb 0ccccddd

statt ohne: 0;

0000aaab bccccddd

Das Feld mit der Breite 0 gibt an, dass die folgenden Bitfelder für die nächste atomare Entität festgelegt werden sollen (char).

8

Meine "versteckte" Lieblingsfunktion von C ist die Verwendung von% n in printf, um zurück in den Stapel zu schreiben. Normalerweise werden die Parameterwerte von printf basierend auf der Formatzeichenfolge vom Stapel abgerufen, aber% n kann sie zurückschreiben.

Lesen Sie Abschnitt 3.4.2 hier . Kann zu vielen bösen Sicherheitslücken führen.

8
Sridhar Iyer

die Verwendung von INT (3) zum Setzen des Haltepunkts am Code ist mein absoluter Favorit

8
Dror Helper

Makros für variable Argumente im C99-Stil, auch bekannt als

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

das würde gerne genutzt werden

ERR(errCantOpen, "File %s cannot be opened", filename);

Hier verwende ich auch den Stringize-Operator und die String-Konstantenkonzentration, andere Funktionen, die ich wirklich mag.

7
Ben Combee

In einigen Fällen sind auch automatische Variablen mit variabler Größe nützlich. Diese wurden in nC99 hinzugefügt und werden seit langem in gcc unterstützt.

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

Sie erhalten einen Puffer auf dem Stapel mit Platz für den Protokollheader mit fester Größe und Daten mit variabler Größe. Sie können den gleichen Effekt mit alloca () erzielen, aber diese Syntax ist kompakter.

Sie müssen sicherstellen, dass extraPadding ein vernünftiger Wert ist, bevor Sie diese Routine aufrufen, oder Sie sprengen den Stack. Sie müssten die Argumente gründlich prüfen, bevor Sie malloc oder eine andere Speicherzuweisungstechnik aufrufen. Dies ist also nicht wirklich ungewöhnlich.

6
DGentry

Mir haben die Strukturen mit variabler Größe gefallen, die Sie erstellen können:

typedef struct {
    unsigned int size;
    char buffer[1];
} tSizedBuffer;

tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));

// can now refer to buff->buffer[0..99].

Auch das offsetof-Makro, das jetzt in ANSI C ist, aber ein Stück Zauberei war, als ich es zum ersten Mal sah. Grundsätzlich wird der Adressoperator (&) für eine Neufassung des Nullzeigers als Strukturvariable verwendet.

5
paxdiablo
5
Steve Webb

Lambda's (z. B. anonyme Funktionen) in GCC:

#define lambda(return_type, function_body) \
    ({ return_type fn function_body fn })

Dies kann verwendet werden als:

lambda (int, (int x, int y) { return x > y; })(1, 2)

Welches ist erweitert in:

({ int fn (int x, int y) { return x > y } fn; })(1, 2)
5
Joe D

Zum Löschen des Eingabepuffers können Sie fflush(stdin) nicht verwenden. Die korrekte Vorgehensweise lautet wie folgt: scanf("%*[^\n]%*c") Damit wird alles aus dem Eingabepuffer gelöscht.

4
Tomas Senart

Das habe ich erst nach mehr als 15 Jahren C-Programmierung entdeckt:

struct SomeStruct
{
   unsigned a : 5;
   unsigned b : 1;
   unsigned c : 7;
};

Bitfelder! Die Zahl nach dem Doppelpunkt ist die Anzahl der Bits, die das Mitglied benötigt, wobei die Mitglieder in den angegebenen Typ gepackt sind. Wenn also 16 Bits vorzeichenlos sind, sieht das oben Gesagte folgendermaßen aus:

xxxc cccc ccba aaaa

Skizz

3
Skizz

Konvertierung von Typen mit ungewöhnlichen Typecasts. Obwohl es kein verstecktes Feature ist, ist es ziemlich knifflig.

Beispiel:

Wenn Sie wissen möchten, wie Compiler-Stores fließen, versuchen Sie Folgendes:

uint32_t Int;
float flt = 10.5; // say

Int = *(uint32_t *)&flt;

printf ("Float 10.5 is stored internally as %8X\n", Int);

oder

float flt = 10.5; // say

printf ("Float 10.5 is stored internally as %8X\n", *(uint32_t *)&flt);

Beachten Sie die clevere Verwendung von Typecasts. Konvertieren der Adresse der Variablen (hier & flt) in den gewünschten Typ (hier (uint32_t *)) und Extrahieren des Inhalts (Anwenden von '*').

Dies funktioniert auch auf der anderen Seite des Ausdrucks:

*(float *)&Int = flt;

Dies könnte auch mit union erreicht werden:

typedef union
{
  uint32_t Int;
  float    flt;

} FloatInt_type;
3
yogeesh

Frühe Versionen von gcc versuchten, ein Spiel auszuführen, wenn es im Quellcode auf "#pragma" stieß. Siehe auch hier .

3
Sec

Ich habe dies einmal in ein bisschen Code gezeigt bekommen und gefragt, was es getan hat:


hexDigit = "0123456789abcdef"[someNybble];

Ein weiterer Favorit ist:


unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];
3

Wenn Sie eine Variable mit einem Literal vergleichen, ist es besser, das Literal an den Operator links des Operators == Zu setzen, um sicherzustellen, dass der Compiler einen Fehler ausgibt, wenn Sie die Zuweisung versehentlich verwenden Operator statt.

if (0 == count) {
    ...
}

Sieht auf den ersten Blick vielleicht komisch aus, kann aber Kopfschmerzen ersparen (zB wenn Sie versehentlich if (count = 0) eingeben).

2
Vicky Chijwani

Steve Webb hat auf die Makros __LINE__ Und __FILE__ Hingewiesen. Es erinnert mich daran, wie ich sie in meinem vorherigen Job gehackt hatte, um im Speicher zu protokollieren.

Ich habe an einem Gerät gearbeitet, auf dem kein Anschluss verfügbar war, um Protokollierungsinformationen vom Gerät an den PC zu übertragen, der zum Debuggen verwendet wird. Man könnte Haltepunkte verwenden, um den Status des Programms mithilfe des Debuggers anzuhalten und zu kennen, aber es gab keine Informationen zum System-Trace.

Da alle Aufrufe von Debug-Protokollen ein einziges globales Makro waren, haben wir dieses Makro geändert, um den Dateinamen und die Zeilennummer in einem globalen Array abzulegen. Dieses Array enthielt eine Reihe von Dateinamen und Zeilennummern, aus denen hervorgeht, welche Debug-Aufrufe aufgerufen wurden. Dies vermittelt einen guten Eindruck von der Ausführungsverfolgung (allerdings nicht von der eigentlichen Protokollnachricht). Man könnte die Ausführung per Debugger anhalten, diese Bytes in eine lokale Datei kopieren und diese Informationen dann mit Hilfe von Skripten der Codebasis zuordnen. Dies wurde ermöglicht, weil wir strenge Codierungsrichtlinien hatten, sodass wir Änderungen am Protokollierungsmechanismus in einer Datei vornehmen mussten.

2
thequark

intptr_t zum Deklarieren von Variablen vom Typ pointer. C99 spezifisch und in stdint.h deklariert

2
user445161

Nicht wirklich ein verstecktes Feature, aber es sah für mich wie Voodoo aus, als ich zum ersten Mal so etwas sah:


void callback(const char *msg, void *data)
{
    // do something with msg, e.g.
    printf("%s\n", msg);

    return;
    data = NULL;
}

Der Grund für diese Konstruktion ist, dass wenn Sie dies mit -Wextra und ohne die "data = NULL;" - Zeile kompilieren, gcc eine Warnung über nicht verwendete Parameter ausgibt. Aber mit dieser nutzlosen Zeile erhalten Sie keine Warnung.

EDIT: Ich weiß, dass es andere (bessere) Möglichkeiten gibt, diese Warnungen zu verhindern. Es sah für mich nur seltsam aus, als ich das zum ersten Mal sah.

2
quinmars

Die Größe der Funktionszeiger ist nicht Standard. Zumindest nicht im K & R-Buch. Auch wenn es um die Größe anderer Zeigertypen geht, ist (glaube ich) sizeof eines Funktionszeigers undefiniertes Verhalten.

Auch sizeof ist ein Operator zur Kompilierungszeit. Ich sehe viele Leute, die fragen, ob sizeof eine Funktion oder ein Operator in Online-Foren ist.

Ein Fehler, den ich gesehen habe, ist wie folgt (ein vereinfachtes Beispiel):

int j;
int i;
j = sizeof(i++)

das Inkrement für i wird nicht ausgeführt, da sizeof zur Kompilierungszeit ausgewertet wird. Der Programmierer beabsichtigte, beide Operationen zu hacken, i zu erhöhen und sizeof in einer Anweisung zu berechnen.

Der Vorrang des Operators in C regelt die Reihenfolge der Zuordnung, nicht die Reihenfolge der Bewertung. Wenn Sie beispielsweise drei Funktionen f, g, h haben, die jeweils ein int zurückgeben, und deren Ausdruck wie folgt lautet:

f() + g() * h()

Der C-Standard gibt keine Regeln für die Reihenfolge der Auswertung dieser Funktionen an. Das Ergebnis von g und h wird multipliziert, bevor das Ergebnis von f hinzugefügt wird. Dies kann zu Fehlern führen, wenn die Funktionen den Status gemeinsam nutzen und die Berechnung von der Reihenfolge der Auswertung dieser Funktionen abhängt. Dies kann zu Portabilitätsproblemen führen.

1
thequark

Auszug :

Auf dieser Seite finden Sie eine Liste interessanter C-Programmierfragen/-puzzles. Diese Programme sind die, die ich von meinen Freunden per E-Mail erhalten habe. Einige habe ich in Büchern gelesen, einige aus dem Internet. und ein paar von meinen Codierungserfahrungen in C.

http://www.gowrikumar.com/c/index.html

1
Özgür

variablen registrieren

Ich habe einige Variablen mit dem Schlüsselwort register deklariert, um die Dinge zu beschleunigen. Dies würde dem C-Compiler einen Hinweis geben, ein CPU-Register als lokalen Speicher zu verwenden. Dies ist höchstwahrscheinlich nicht mehr erforderlich, da moderne C-Compiler dies automatisch tun.

1
Mark Stock

Angenommen, Sie haben eine Struktur mit Mitgliedern desselben Typs:

struct Point {
    float x;
    float y;
    float z;
};

Sie können Instanzen davon in einen Float-Zeiger umwandeln und Array-Indizes verwenden:

Point a;
int sum = 0, i = 0;
for( ; i < 3; i++)
    sum += ((float*)a)[i];

Ziemlich einfach, aber nützlich beim Schreiben von prägnantem Code.

1
aeflash

Das oft vergessene %n Bezeichner im printf Format können manchmal sehr praktisch sein. % n gibt die aktuelle Position des imaginären Cursors zurück, der verwendet wird, wenn printf seine Ausgabe formatiert.

int pos1, pos2;
 char *string_of_unknown_length = "we don't care about the length of this";

  printf("Write text of unknown %n(%s)%n text\n", &pos1, string_of_unknown_length, &pos2);
  printf("%*s\\%*s/\n", pos1, " ", pos2-pos1-2, " ");
  printf("%*s", pos1+1, " ");
  for(int i=pos1+1; i<pos2-1; i++)
    putc('-', stdout);
  putc('\n', stdout);

wird folgende Ausgabe haben

Write text of unknown (we don't care about the length of this) text
                      \                                      /
                       --------------------------------------

Zugegeben, ein bisschen durchdacht, kann aber bei der Erstellung hübscher Berichte nützlich sein.

0

Verwenden Sie NaN für verkettete Berechnungen/Fehlerrückgabe:

// # include <stdint.h>
static uint64_t iNaN = 0xFFF8000000000000;
const double NaN = * (double *) & iNaN; // leise NaN

Eine innere Funktion kann NaN als Fehlerflag zurückgeben: Sie kann sicher in jeder Berechnung verwendet werden, und das Ergebnis ist immer NaN.

hinweis: Das Testen auf NaN ist schwierig, da NaN! = NaN ... isnan (x) verwendet oder dein eigenes gewürfelt wird.
x! = x ist mathematisch korrekt, wenn x NaN ist, wird aber von einigen Compilern optimiert

0
Adrian Sietsma

Ich mag den Operator typeof (). Es funktioniert wie sizeof (), da es zur Kompilierungszeit aufgelöst wird. Anstatt die Anzahl der Bytes zurückzugeben, wird der Typ zurückgegeben. Dies ist nützlich, wenn Sie eine Variable als vom gleichen Typ wie eine andere Variable deklarieren müssen, unabhängig vom Typ.

typeof(foo) copy_of_foo; //declare bar to be a variable of the same type as foo
copy_of_foo = foo; //now copy_of_foo has a backup of foo, for any type

Dies könnte nur eine GCC-Erweiterung sein, da bin ich mir nicht sicher.

0
Eyal

Ich habe gerade gelesen Artikel . Es hat einige C und mehrere andere Sprachen "versteckte Funktionen".

0
Rigo Vides

Wie wäre es, while (0) in einem Schalter zu verwenden, um continue-Anweisungen wie break zu verwenden :-)

void sw(int s)
{
    switch (s) while (0) {
    case 0:
        printf("zero\n");
        continue;
    case 1:
        printf("one\n");
        continue;
    default:
        printf("something else\n");
        continue;
    }
}
0
Colin King