Versteckte Funktionen von C.


141

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.


Es wäre großartig, wenn Sie / jemand die „Frage“ bearbeiten würde, um die Auswahl der besten versteckten Funktionen anzugeben, z. B. in den C # - und Perl-Versionen dieser Frage.
Donal Fellows

Antworten:


62

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

Dann gibt es versteckte Juwelen in der Standardbibliothek, wie z. B. qsort (), bsearch (), strpbrk (), strcspn () [die beiden letzteren sind nützlich für die Implementierung eines strtok () - Ersatzes].

Eine Fehlfunktion von C ist, dass ein vorzeichenbehafteter arithmetischer Überlauf ein undefiniertes Verhalten (UB) ist. Wenn Sie also einen Ausdruck wie x + y sehen, bei dem es sich um signierte Ints handelt, kann er möglicherweise überlaufen und UB verursachen.


29
Wenn sie jedoch das Verhalten beim Überlauf angegeben hätten, wäre es bei Architekturen, bei denen dies nicht das normale Verhalten war, sehr langsam geworden. Sehr geringer Laufzeitaufwand war schon immer ein Entwurfsziel von C, und das hat dazu geführt, dass viele Dinge wie diese undefiniert sind.
Mark Baker

9
Mir ist sehr gut bewusst, warum Überlauf UB ist. Es ist immer noch eine Fehlfunktion, da der Standard zumindest Bibliotheksroutinen bereitgestellt haben sollte, die auf arithmetischen Überlauf (aller grundlegenden Operationen) testen können, ohne UB zu verursachen.
Zvrba

2
@zvrba, "Bibliotheksroutinen, die auf arithmetischen Überlauf (aller Grundoperationen) testen können" Wenn Sie dies hinzugefügt hätten, hätten Sie für alle ganzzahligen arithmetischen Operationen einen erheblichen Leistungseinbruch erlitten. ===== Fallstudie Matlab fügt die Funktion zur Steuerung des Ganzzahlüberlaufverhaltens speziell dem Umschließen oder Sättigen hinzu. Außerdem wird bei jedem Überlauf eine Ausnahme ausgelöst ==> Leistung von Matlab-Ganzzahloperationen: SEHR LANGSAM. Meine eigene Schlussfolgerung: Ich denke, Matlab ist eine überzeugende Fallstudie, die zeigt, warum Sie keine Ganzzahlüberlaufprüfung wünschen.
Trevor Boyd Smith

15
Ich sagte, dass der Standard Bibliotheksunterstützung für die Überprüfung auf arithmetischen Überlauf bieten sollte . Wie kann eine Bibliotheksroutine einen Leistungseinbruch verursachen, wenn Sie sie nie verwenden?
Zvrba

5
Ein großer Nachteil ist, dass GCC kein Flag hat, um vorzeichenbehaftete Ganzzahlüberläufe abzufangen und eine Laufzeitausnahme auszulösen. Es gibt zwar x86-Flags zum Erkennen solcher Fälle, GCC verwendet sie jedoch nicht. Ein solches Flag würde nicht leistungskritischen (insbesondere älteren) Anwendungen den Vorteil der Sicherheit mit minimaler bis keiner Codeüberprüfung und Umgestaltung ermöglichen.
Andrew Keeton

116

Eher ein Trick des GCC-Compilers, aber Sie können dem Compiler Hinweise zur Verzweigungsanzeige 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 mir daran gefällt, ist, dass es einigen Funktionen auch etwas Ausdruckskraft verleiht.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
Dieser Trick ist cool ... :) Besonders mit den von Ihnen definierten Makros. :)
Sundar - Wiedereinsetzung Monica

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Dies ist ein optionales Element im Standard, aber es muss eine versteckte Funktion sein, da die Leute sie ständig neu definieren. Eine Codebasis, an der ich gearbeitet habe (und vorerst noch arbeite), hat mehrere Neudefinitionen, alle mit unterschiedlichen Bezeichnern. Meistens mit Präprozessor-Makros:

#define INT16 short
#define INT32  long

Und so weiter. Ich möchte mir die Haare ausreißen. Verwenden Sie einfach die verdammten Standard-Integer-Typedefs!


3
Ich denke, sie sind C99 oder so. Ich habe keinen tragbaren Weg gefunden, um sicherzustellen, dass diese vorhanden sind.
Akauppi

3
Sie sind ein optionaler Bestandteil von C99, aber ich kenne keine Compiler-Anbieter, die dies nicht implementieren.
Ben Collins

10
stdint.h ist in C99 nicht optional, aber das Befolgen des C99-Standards gilt anscheinend für einige Anbieter ( Husten Microsoft).
Ben Combee

5
@Pete, wenn du anal sein willst: (1) Dieser Thread hat nichts mit Microsoft-Produkten zu tun. (2) Dieser Thread hatte überhaupt nichts mit C ++ zu tun. (3) Es gibt kein C ++ 97.
Ben Collins

5
Werfen Sie einen Blick auf azillionmonkeys.com/qed/pstdint.h - eine nahezu tragbare stdint.h
gnud

73

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 ist der der zuletzt ausgewerteten Anweisung.


7
In 20 Jahren von CI habe ich das NIE gesehen!
Martin Beckett

11
In C ++ können Sie es sogar überladen.
Wouter Lievens

6
kann! = sollte natürlich. Die Gefahr bei Überlastung besteht darin, dass das Eingebaute bereits für alles gilt, einschließlich void, und daher mangels verfügbarer Überlastung immer wieder kompiliert werden kann. Dh gibt dem Programmierer viel Seil.
Aaron

Das int in der Schleife funktioniert nicht mit C: Es ist eine C ++ - Verbesserung. Ist das "," die gleiche Operation wie für (i = 0, j = 10; i <j; j--, i ++)?
Aif

63

Struktur auf Null initialisieren

struct mystruct a = {0};

Dadurch werden alle Strukturelemente auf Null gesetzt.


2
Die Polsterung wird jedoch nicht auf Null gesetzt, falls vorhanden.
Mikeage

2
@simonn, nein, es macht kein undefiniertes Verhalten, wenn die Struktur nicht integrale Typen enthält. memset mit 0 im Speicher eines float / double ist immer noch Null, wenn Sie float / double interpretieren (float / double sind absichtlich so gestaltet).
Trevor Boyd Smith

6
@ Andrew: memset/ callocmache "alle Bytes Null" (dh physikalische Nullen), was in der Tat nicht für alle Typen definiert ist. { 0 } garantiert, dass alles mit den richtigen logischen Nullwerten intilaisiert wird . Es wird beispielsweise garantiert, dass Zeiger ihre richtigen Nullwerte erhalten, selbst wenn der Nullwert auf der angegebenen Plattform ist 0xBAADFOOD.
AnT

1
@nvl: Sie erhalten eine physische Null, wenn Sie den gesamten vom Objekt belegten Speicher zwangsweise auf den Status "Alle Bits Null" setzen. Dies ist, was memsettut (mit 0als zweites Argument). Sie erhalten eine logische Null, wenn Sie das Objekt im Quellcode initialisieren / zuweisen 0(oder { 0 }). Diese beiden Arten von Nullen führen nicht unbedingt zum gleichen Ergebnis. Wie im Beispiel mit Zeiger. Wenn Sie memseteinen Zeiger verwenden, erhalten Sie einen 0x0000Zeiger. Wenn Sie 0jedoch einem Zeiger zuweisen , erhalten Sie einen Nullzeigerwert , der auf der physischen Ebene 0xBAADF00Doder etwas anderes sein kann.
Am

3
@nvl: Nun, in der Praxis ist der Unterschied oft nur konzeptionell. Aber theoretisch kann es praktisch jeder Typ haben. Zum Beispiel double. Normalerweise wird es gemäß dem IEEE-754-Standard implementiert, bei dem die logische Null und die physikalische Null gleich sind. IEEE-754 wird jedoch von der Sprache nicht benötigt. Es kann also vorkommen, dass double d = 0;einige Bits im Speicher, die von belegt dwerden, nicht Null sind , wenn Sie dies tun (logische Null) .
Am

52

Konstanten mit mehreren Zeichen:

int x = 'ABCD';

Dies setzt xauf 0x41424344(oder 0x44434241, abhängig von der Architektur).

BEARBEITEN: Diese Technik ist nicht portierbar, insbesondere wenn Sie den 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 sich einen Rohspeicherauszug ansehen und den Wert einer Aufzählung ermitteln müssen, ohne ihn nachschlagen zu müssen.


Ich bin mir ziemlich sicher, dass dies kein tragbares Konstrukt ist. Das Ergebnis der Erstellung einer Konstante mit mehreren Zeichen ist implementierungsdefiniert.
Mark Bessey

8
Die "nicht tragbaren" Kommentare verfehlen den Punkt völlig. Es ist, als würde man ein Programm für die Verwendung von INT_MAX kritisieren, nur weil INT_MAX "nicht portabel" ist :) Diese Funktion ist so portabel, wie es sein muss. Die Multi-Char-Konstante ist eine äußerst nützliche Funktion, die eine lesbare Möglichkeit zum Generieren eindeutiger Ganzzahl-IDs bietet.
AnT

1
@ Chris Lutz - Ich bin mir ziemlich sicher, dass das nachfolgende Komma bis zu K & R zurückreicht. Es ist in der zweiten Ausgabe (1988) beschrieben.
Ferruccio

1
@Ferruccio: Sie müssen über das nachfolgende Komma in den aggregierten Initailizer-Listen nachdenken. Das nachfolgende Komma in Enum-Deklarationen wurde kürzlich hinzugefügt, C99.
AnT

3
Du hast 'HANG' oder 'BSOD' vergessen :-)
JBRWilkinson

44

Ich habe nie Bitfelder verwendet, aber sie klingen cool für Sachen mit extrem niedrigem Pegel.

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, sizeof(cat)dass so klein sein kann wie sizeof(char).


Eingebaute Kommentare von Aaron und Leppie , danke Jungs.


Die Kombination von Strukturen und Gewerkschaften ist noch interessanter - auf eingebetteten Systemen oder Treibercode auf niedriger Ebene. Ein Beispiel ist, wenn Sie die Register einer SD-Karte analysieren möchten, können Sie sie mit union (1) einlesen und mit union (2) auslesen, einer Struktur von Bitfeldern.
ComSubVie

5
Bitfelder sind nicht portierbar - der Compiler kann frei wählen, ob in Ihrem Beispiel den Beinen die höchstwertigen 3 Bits oder die niedrigstwertigen 3 Bits zugewiesen werden.
Zvrba

3
Bitfelder sind ein Beispiel dafür, wo der Standard Implementierungen so viel Freiheit bei der Implementierung gibt, dass sie in der Praxis nahezu nutzlos sind. Wenn Sie sich darum kümmern, wie viele Bits ein Wert aufnimmt und wie er gespeichert wird, sollten Sie Bitmasken verwenden.
Mark Bessey

26
Bitfelder sind in der Tat portabel, solange Sie sie als die Strukturelemente behandeln, die sie sind, und nicht als "Ganzzahlstücke". Größe, nicht Standort, spielt in einem eingebetteten System mit begrenztem Speicher eine Rolle, da jedes Bit wertvoll ist ... aber die meisten heutigen Codierer sind zu jung, um sich daran zu erinnern. :-)
Adam Liss

5
@Adam: Die Position kann in einem eingebetteten System (oder anderswo) von Bedeutung sein, wenn Sie von der Position des Bitfelds innerhalb seines Bytes abhängen. Durch die Verwendung von Masken werden Mehrdeutigkeiten beseitigt. Ähnliches gilt für Gewerkschaften.
Steve Melnikoff

37

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

Die Tricks, die ich bevorzuge, sind jedoch nicht offensichtlich und plattformübergreifend portierbar, da sie auf der C-Semantik beruhen. In der Regel handelt es sich um Makros oder Bitarithmetik.

Beispiel: Vertauschen von zwei Ganzzahlen ohne Vorzeichen 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 klug sind, aber das Lesen des Codes unnötig kompliziert machen (als Swap-Beispiel), und ich liebe diejenigen, die den Code klarer machen und die Absicht direkt vermitteln (wie das FSM-Beispiel). .


18
C unterstützt die Verkettung, sodass Sie a ^ = b ^ = a ^ = b ausführen können.
ABl.

4
Genau genommen ist das Zustandsbeispiel ein Häkchen des Präprozessors und nicht der C-Sprache - es ist möglich, die erstere ohne die letztere zu verwenden.
Greg Whitfield

15
ABl.: Eigentlich schlagen Sie undefiniertes Verhalten aufgrund von Sequenzpunktregeln vor. Es funktioniert möglicherweise auf den meisten Compilern, ist jedoch nicht korrekt oder portabel.
Evan Teran

5
Xor-Swap könnte im Fall eines freien Registers weniger effizient sein. Jeder anständige Optimierer würde die temporäre Variable zu einem Register machen. Abhängig von der Implementierung (und dem Bedarf an Parallelitätsunterstützung) verwendet der Swap möglicherweise tatsächlich realen Speicher anstelle eines Registers (das dasselbe wäre).
Paul de Vrieze

27
Bitte tun Sie dies niemals wirklich: en.wikipedia.org/wiki/…
Christian Oudard

37

Interlacing-Strukturen wie Duff's Device :

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

29
@ComSubVie, jeder, der Duffs Gerät verwendet, ist ein Skriptkind, das Duffs Gerät gesehen hat und dachte, sein Code würde 1337 aussehen, wenn er Duffs Gerät verwendet. (1.) Duffs Gerät bietet auf modernen Prozessoren keine Leistungssteigerungen, da moderne Prozessoren keine Overhead-Schleifen aufweisen. Mit anderen Worten, es ist ein veralteter Code. (2.) Selbst wenn Ihr Prozessor keine Null-Overhead-Schleife bietet, wird er wahrscheinlich so etwas wie SSE / altivec / vector-Verarbeitung haben, was Ihr Duff's Device beschämen wird, wenn Sie memcpy () verwenden. (3.) Habe ich erwähnt, dass es nicht sinnvoll ist, memcpy () duff's zu machen?
Trevor Boyd Smith

2
@ ComSubVie, bitte treffen Sie meine Faust des Todes ( en.wikipedia.org/wiki/… )
Trevor Boyd Smith

12
@ Trevor: Also nur Script Kiddies Programm 8051 und PIC Mikrocontroller, oder?
SF.

6
@ Trevor Boyd Smith: Obwohl das Gerät des Duff veraltet erscheint, ist es immer noch eine historische Kuriosität, die die Antwort von ComSubVie bestätigt. Wie auch immer, zitiert Wikipedia: "Als in Version 4.0 zahlreiche Instanzen von Duffs Gerät vom XFree86-Server entfernt wurden, gab es eine bemerkenswerte Verbesserung der Leistung." ...
Paercebal

2
Unter Symbian haben wir einmal verschiedene Schleifen für eine schnelle Pixelcodierung ausgewertet. Das Gerät des Duffs im Assembler war das schnellste. Daher hatte es auch heute noch Relevanz für die gängigen ARM-Kerne auf Ihren Smartphones.
Wird

33

Ich mag bestimmte Initialisierer, die in C99 hinzugefügt wurden (und lange Zeit in gcc unterstützt wurden):

#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.


Die Syntax, die gcc seit langem unterstützt, entspricht nicht der Standard-C99-Syntax.
Mark Baker

28

C99 verfügt über eine fantastische Strukturinitialisierung beliebiger Reihenfolge.

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

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}


27

anonyme Strukturen und Arrays sind meine Favoriten. (vgl. 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 ...


3
Diese Funktion wird normalerweise als "zusammengesetzte Literale" bezeichnet. Anonyme (oder unbenannte) Strukturen bezeichnen verschachtelte Strukturen ohne Mitgliedsnamen.
Calandoa

Laut meinem GCC verbietet "ISO C90 zusammengesetzte Literale".
jmtd

"ISO C99 unterstützt zusammengesetzte Literale." "Als Erweiterung unterstützt GCC zusammengesetzte Literale im C89-Modus und in C ++" (dixit info gcc). Plus: "Als GNU-Erweiterung ermöglicht GCC die Initialisierung von Objekten mit statischer Speicherdauer durch zusammengesetzte Literale (was in ISO C99 nicht möglich ist, da der Initialisierer keine Konstante ist)."
PypeBros

24

gcc hat eine Reihe von Erweiterungen der C-Sprache, die mir gefallen und die hier zu finden sind . 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 Ihre Argumente übereinstimmen, und generiert gegebenenfalls Warnungen oder Fehler.

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

24

Die (versteckte) Funktion, die mich "schockierte", als ich sie zum ersten Mal sah, handelt von printf. Mit dieser Funktion können Sie Variablen zum Formatieren von Formatspezifizierern 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

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 tragbar wie möglich.


Aber wie oft müssen Sie Ihren Code in Wirklichkeit mit einem anderen Compiler kompilieren?
Joe D

3
@ Joe D, wenn es ein plattformübergreifendes Projekt wie Windows / OSX / Linux ist, wahrscheinlich ein bisschen, und es gibt auch verschiedene Arch wie x86 vs x86_64 und etc ...
Pharaun

@ JoeD Es sei denn, Sie befinden sich in einem sehr engstirnigen Projekt, das gerne einen Compiler-Anbieter heiratet. Möglicherweise möchten Sie vermeiden, dass Sie den Compiler wechseln müssen, aber Sie möchten diese Option offen lassen. Bei eingebetteten Systemen haben Sie jedoch nicht immer die Wahl. AHS, ASS.
XTL

19

Behauptungen zur Kompilierungszeit, wie hier bereits erläutert .

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

16

Konstante Zeichenfolgenverkettung

Ich war ziemlich überrascht, dass ich es nicht bereits in den Antworten gesehen habe, 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 eine #define PATH "/some/path/"in einer Konfigurationsdatei (wirklich wird sie 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 des schrecklichen, aber sehr häufigen:

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 auf eine beliebige Puffergrößenbeschränkung eingestellt (aber Sie müssten noch längeren Code verwenden, um dies ohne ständige Verkettung von Zeichenfolgen zu vermeiden).
  • Verwenden Sie mehr Stapelplatz

1
Es ist auch nützlich, eine Zeichenfolgenkonstante auf mehrere Quellzeilen aufzuteilen, ohne schmutziges `\` zu verwenden.
Dolmen

15

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


12

Beim Initialisieren von Arrays oder Aufzählungen können Sie nach dem letzten Element 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.


Dies ist auch in einer Umgebung mit mehreren Entwicklern wichtig, in der beispielsweise Eric "baz" und George "boom" hinzufügt. Wenn Eric beschließt, seinen Code für den nächsten Projektaufbau herauszuholen, wird er dennoch mit Georges Änderung kompiliert. Sehr wichtig für die Steuerung von Quellcode mit mehreren Zweigen und überlappende Entwicklungspläne.
Harold Bamford

Aufzählungen können C99 sein. Array-Initialisierer und das nachfolgende Komma sind K & R.
Ferruccio

Einfache Aufzählungen waren in c89, AFAIK. Zumindest gibt es sie schon seit Ewigkeiten.
XTL

12

Strukturzuordnung ist cool. Viele Leute scheinen nicht zu erkennen, dass Strukturen auch Werte sind und herum zugewiesen werden können, es besteht keine Notwendigkeit, sie zu verwenden memcpy(), wenn eine einfache Zuweisung den Trick macht.

Betrachten Sie beispielsweise eine imaginäre 2D-Grafikbibliothek, die einen Typ definiert, der eine (ganzzahlige) Bildschirmkoordinate darstellt:

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

Jetzt tun Sie Dinge, die möglicherweise "falsch" aussehen, z. B. eine Funktion schreiben, die einen aus Funktionsargumenten initialisierten Punkt erstellt und 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 mithilfe der Strukturzuweisung durch den Wert kopiert wird:

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

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


4
Natürlich hat die Weitergabe großer Strukturen auf diese Weise Auswirkungen auf die Leistung. Es ist oft nützlich (und in der Tat wissen viele Leute nicht, dass Sie es können), aber Sie müssen überlegen, ob das Übergeben von Zeigern besser ist.
Mark Baker

1
Natürlich könnte es sein. Es ist dem Compiler auch durchaus möglich, die Nutzung zu erkennen und zu optimieren.
Entspannen Sie

Seien Sie vorsichtig, wenn eines der Elemente Zeiger sind, da Sie die Zeiger selbst kopieren und nicht deren Inhalt. Das gleiche gilt natürlich auch, wenn Sie memcpy () verwenden.
Adam Liss

Der Compiler kann diese Konvertierung von By-Value-Übergaben mit By-Referenece nicht optimieren, es sei denn, er kann globale Optimierungen durchführen.
Blaisorblade

Es ist wahrscheinlich erwähnenswert, dass der Standard in C ++ speziell das Optimieren der Kopie ermöglicht (der Standard muss es Compilern ermöglichen, sie zu implementieren, da dies bedeutet, dass der Kopierkonstruktor, der möglicherweise Nebenwirkungen hat, möglicherweise nicht aufgerufen wird), und da die meisten C ++ - Compiler Sind auch C-Compiler, besteht eine gute Chance, dass Ihr Compiler diese Optimierung durchführt.
Joseph Garvin

10

Seltsame Vektorindizierung:

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

4
Es ist sogar noch besser ... char c = 2 ["Hallo"]; (c == 'l' danach).
25.

5
Nicht so seltsam, wenn man bedenkt, dass v [index] == * (v + index) und index [v] == * (index + v)
Ferruccio

17
Bitte sagen Sie mir, dass Sie dies nicht "die ganze Zeit" verwenden, wie die Frage stellt!
Tryke

9

C-Compiler implementieren einen von mehreren Standards. Ein Standard bedeutet jedoch nicht, dass alle Aspekte der Sprache definiert sind. Das Gerät von Duff ist beispielsweise eine beliebte "versteckte" Funktion, die 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 von versteckten Funktionen oder Sprachtricks abgeraten, wenn Sie auf der Rasierklinge der C-Standards arbeiten, die Ihr Compiler verwendet. Viele solcher Tricks funktionieren nicht von einem Compiler zum anderen, und häufig schlagen diese Funktionen von einer Version einer Compilersuite eines bestimmten Herstellers zu einer anderen Version fehl.

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

  1. Verlassen Sie sich darauf, wie der Compiler Strukturen im Speicher anordnet.
  2. Annahmen zur Endianness von ganzen Zahlen / Gleitkommazahlen.
  3. Annahmen zu Funktions-ABIs.
  4. Annahmen über die Richtung, in die 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 alle in den meisten C-Standards als "compilerabhängiges" Verhalten angegeben sind.


Um die meisten dieser Probleme zu lösen, machen Sie diese Annahmen von den Eigenschaften Ihrer Plattform abhängig und beschreiben Sie jede Plattform in ihrem eigenen Header. Die Ausführung von Aufträgen ist eine Ausnahme - verlassen Sie sich niemals darauf. Auf der anderen Seite muss jede Plattform eine verlässliche Entscheidung treffen.
Blaisorblade

2
@Blaisorblade, Verwenden Sie noch besser Zusicherungen zur Kompilierungszeit, um Ihre Annahmen so zu dokumentieren, dass die Kompilierung auf einer Plattform fehlschlägt, auf der sie verletzt werden.
RBerteig

Ich denke, man sollte beide kombinieren, damit Ihr Code auf mehreren Plattformen funktioniert (das war die ursprüngliche Absicht), und wenn die Feature-Makros falsch eingestellt sind, werden sie durch Zusicherungen zur Kompilierungszeit abgefangen. Ich bin mir nicht sicher, ob beispielsweise Annahmen über Funktions-ABIs als Zusicherungen zur Kompilierungszeit überprüfbar sind, aber es sollte für die meisten anderen (gültigen) Aussagen möglich sein (außer für die Reihenfolge der Ausführung ;-)).
Blaisorblade

Funktions-ABI-Prüfungen sollten von einer Testsuite durchgeführt werden.
Dolmen

9

Wenn Sie sscanf verwenden, können Sie% n verwenden, um herauszufinden, wo Sie weiterlesen sollten:

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

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

#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

8

Die Verwendung von INT (3) zum Festlegen des Haltepunkts am Code ist mein absoluter Favorit


3
Ich denke nicht, dass es tragbar ist. Es wird auf x86 funktionieren, aber was ist mit anderen Plattformen?
Cristian Ciupitu

1
Ich habe keine Ahnung - Sie sollten eine Frage dazu posten
Dror Helper

2
Es ist eine gute Technik und X86-spezifisch (obwohl es wahrscheinlich ähnliche Techniken auf anderen Plattformen gibt). Dies ist jedoch keine Funktion von C. Es hängt von nicht standardmäßigen C-Erweiterungen oder Bibliotheksaufrufen ab.
Ferruccio

1
In GCC gibt es __builtin_trap und für MSVC __debugbreak, das auf jeder unterstützten Architektur funktioniert.
Axel Gneiting

8

Meine bevorzugte "versteckte" Funktion von C ist die Verwendung von% n in printf, um auf den Stapel zurückzuschreiben. Normalerweise werden die Parameterwerte von printf basierend auf der Formatzeichenfolge aus dem Stapel entfernt, aber% n kann sie zurückschreiben.

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


Der Link funktioniert nicht mehr, tatsächlich scheint die Site selbst nicht zu funktionieren. Können Sie einen anderen Link bereitstellen?
Thequark

@thequark: Jeder Artikel über "Sicherheitslücken beim Formatieren von Zeichenfolgen" enthält einige Informationen. (z. B. crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ). Aufgrund der Art des Felds ist die Sicherheit jedoch gegeben Websites selbst sind etwas schuppig und echte akademische Artikel sind schwer zu bekommen (mit Implementierung).
Sridhar Iyer

8

Überprüfung der Annahmen zur Kompilierungszeit mithilfe von Aufzählungen: Dummes 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)
};

2
+1 Ordentlich. Ich habe früher das CompilerAssert-Makro von Microsoft verwendet, aber deins ist auch nicht schlecht. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter

1
Ich mag die Aufzählungsmethode. Der Ansatz, den ich zuvor verwendet hatte, nutzte die Beseitigung von totem Code: "if (Something_Bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}", der bis zur Verbindungszeit nicht fehlerhaft war, obwohl er den Vorteil bot, den Programmierer wissen per Fehlermeldung, dass der Blorg woozled wurde.
Supercat

8

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


8

Ich habe kürzlich 0 Bitfelder entdeckt.

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

welches 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 gesetzt werden sollen ( char)


7

Makros mit variablen Argumenten im C99-Stil, auch bekannt als

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

das würde gerne verwendet werden

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

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


Sie haben ein zusätzliches 'R' in VA_ARGS .
Blaisorblade

6

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

Am Ende befindet sich ein Puffer auf dem Stapel mit Platz für den Protokollheader mit fester Größe sowie 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 angemessener Wert ist, bevor Sie diese Routine aufrufen. Andernfalls wird der Stapel gesprengt. Sie müssen die Argumente überprüfen, bevor Sie malloc oder eine andere Speicherzuweisungstechnik aufrufen. Dies ist also nicht wirklich ungewöhnlich.


Funktioniert dies auch richtig, wenn ein Byte / Zeichen auf der Zielplattform nicht genau 8 Bit breit ist? Ich weiß, diese Fälle sind selten, aber immer noch ... :)
Stephan202
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.