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.
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.
Antworten:
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.
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();
...
}
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!
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.
Struktur auf Null initialisieren
struct mystruct a = {0};
Dadurch werden alle Strukturelemente auf Null gesetzt.
memset
/ calloc
mache "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
.
memset
tut (mit 0
als 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 memset
einen Zeiger verwenden, erhalten Sie einen 0x0000
Zeiger. Wenn Sie 0
jedoch einem Zeiger zuweisen , erhalten Sie einen Nullzeigerwert , der auf der physischen Ebene 0xBAADF00D
oder etwas anderes sein kann.
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 d
werden, nicht Null sind , wenn Sie dies tun (logische Null) .
Konstanten mit mehreren Zeichen:
int x = 'ABCD';
Dies setzt x
auf 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 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)
.
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). .
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);
}
}
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.
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 ...
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)));
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.
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.
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);
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:
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 .
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.
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.
Seltsame Vektorindizierung:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
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:
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.
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
Die Verwendung von INT (3) zum Festlegen des Haltepunkts am Code ist mein absoluter Favorit
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.
Ü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)
};
#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]
)
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.
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
)
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.
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.