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.
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.
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();
...
}
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!
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.
Struktur auf Null initialisieren
struct mystruct a = {0};
dadurch werden alle Strukturelemente auf Null gesetzt.
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.
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)
.
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);
}
}
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). .
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.
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>
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 ...
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.
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)));
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.
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);
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:
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 =
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.
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.
Seltsame Vektorindizierung:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
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
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:
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.
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.
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)
};
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
).
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.
die Verwendung von INT (3) zum Setzen des Haltepunkts am Code ist mein absoluter Favorit
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.
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.
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.
Ich mag __LINE__
und __FILE__
. Siehe hier: http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
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)
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.
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
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;
Frühe Versionen von gcc versuchten, ein Spiel auszuführen, wenn es im Quellcode auf "#pragma" stieß. Siehe auch hier .
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];
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).
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.
intptr_t zum Deklarieren von Variablen vom Typ pointer. C99 spezifisch und in stdint.h deklariert
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.
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.
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.
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.
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.
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.
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
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.
Ich habe gerade gelesen Artikel . Es hat einige C und mehrere andere Sprachen "versteckte Funktionen".
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;
}
}