Unter anderem hat C99 das Schlüsselwort „restrict“ hinzugefügt, um einem Programmierer die Möglichkeit zu geben, anzugeben, dass ein Zeiger der einzige Zeiger auf ein bestimmtes Objekt in einem Bereich ist, und dem Compiler folglich einen „Hinweis“ zu geben ” dass es zusätzliche Optimierungen durchführen kann, wenn über diesen Zeiger auf das Objekt zugegriffen wird.
Um das Problem zu veranschaulichen, das die Einschränkung lösen sollte, betrachten Sie eine Funktion wie:
void update_ptrs( int *p, int *q, int const *v ) { *p += *v; *q += *v; }
für den der Compiler x86-64-Code wie folgt generiert:
mov eax, [rdx] ; tmp = *v // 1 add [rdi], eax ; *p += tmp mov eax, [rdx] ; tmp = *v // 3 add [rsi], eax ; *q += tmp
Sie fragen sich vielleicht, warum Zeile 3 generiert wird, da sie mit Zeile 1 überflüssig zu sein scheint. Das Problem ist, dass der Compiler nicht wissen kann, dass Sie so etwas nicht getan haben:
int x = 1, v = 2; update_ptrs( &v, &x, &v ); // x = 5, v = 4
In update_ptrs() würden p und v Alias das gleiche int sein, daher muss der Compiler auf Nummer sicher gehen und davon ausgehen, dass sich der Wert von *v zwischen den Lesevorgängen ändern kann. daher die zusätzliche mov-Anweisung.
Im Allgemeinen verfälschen Zeiger in C die Optimierung, da der Compiler nicht wissen kann, ob sich zwei Zeiger gegenseitig aliasen. Bei leistungskritischem Code könnte das Eliminieren von Speicherlesevorgängen ein großer Gewinn sein, wenn der Compiler dies sicher tun könnte.
Um das oben genannte Problem zu lösen, wurde „restrict“ zu C hinzugefügt, damit Sie angeben können, dass ein bestimmter Zeiger der einzige Zeiger auf ein Objekt im Gültigkeitsbereich des Zeigers ist, d. h. kein anderer Zeiger im selben Bereich Aliase es.
Um „restrict“ zu verwenden, fügen Sie es zwischen dem * und dem Namen des Zeigers in einer Deklaration ein. Ein update_ptrs(), das zur Verwendung von „restrict“ umgeschrieben wurde, wäre:
void update_ptrs_v2( int *restrict p, int *restrict q, int const *restrict v ) { *p += *v; *q += *v; }
(Lesen Sie von rechts nach links, z. B. ist v ein eingeschränkter Zeiger auf eine Konstante int; oder verwenden Sie cdecl.)
Durch das Hinzufügen von „restrict“ kann der Compiler jetzt Code generieren wie:
mov eax, [rdx] ; tmp = *v add [rdi], eax ; *p += tmp add [rsi], eax ; *q += tmp
Jetzt konnte der Compiler die vorherige Zeile 3 der zusätzlichen mov-Anweisung eliminieren.
Das vielleicht bekannteste Beispiel für die Verwendung von „Restrict“ ist die Standardbibliotheksfunktion memcpy(). Dies ist der schnellste Weg, einen Teil des Speichers zu kopieren, wenn sich die Quell- und Zieladresse nicht überschneiden. Die etwas langsamere memmove()-Funktion gibt es für den Einsatz, wenn sich die Adressen nicht überlappen.
Missbrauch von „restrict“ führt zu undefiniertem Verhalten, beispielsweise durch die Übergabe von Zeigern, die sich gegenseitig auslösen, an update_ptrs_v2() oder memcpy(). In einigen Fällen kann der Compiler Sie warnen, aber nicht in allen Fällen. Verlassen Sie sich also nicht darauf, dass der Compiler Missbrauch erkennt.
Beachten Sie, dass die Einschränkung für einen bestimmten Bereich gilt. Das Zuweisen eines eingeschränkten Zeigers zu einem anderen im selben Bereich führt zu undefiniertem Verhalten:
void f( int *restrict d, int *restrict s ) { int *restrict p = s; // undefined behavior
Sie können jedoch problemlos einen eingeschränkten Zeiger einem uneingeschränkten Zeiger zuweisen:
void f( int *restrict d, int *restrict s ) { int *p = s; // OK
Auch wenn p uneingeschränkt ist, kann der Compiler dennoch dieselben Optimierungen durchführen.
Es ist auch in Ordnung, einen eingeschränkten Zeiger in einem inneren Bereich einem anderen in einem äußeren Bereich zuzuweisen (aber nicht umgekehrt):
void f( int *restrict d, int *restrict s ) { { // inner scope int *restrict p = s; // OK // ... s = p; // undefined behavior } }
Zuerst sollten Sie auf jeden Fall ein Profil Ihres Codes erstellen (und sich vielleicht sogar den generierten Assembler-Code ansehen), um zu sehen, ob die Verwendung von „restrict“ tatsächlich zu einer erheblichen Leistungsverbesserung führt, um das Risiko potenzieller Fallstricke zu rechtfertigen. Die Diagnose von Fehlern, die durch den Missbrauch von Einschränkungen verursacht werden, ist sehr schwierig.
Zweitens ist es sicherer, wenn die Verwendung von „restrict“ auf die Implementierung einer Funktion beschränkt ist, bei der der Speicher, auf den über eingeschränkte Zeiger zugegriffen wird, von Sie zugewiesen wurde. Zum Beispiel gegeben:
void safer( unsigned n ) { n += n % 2 != 0; // make even by rounding up int *const array = malloc( n * sizeof(unsigned) ); unsigned *restrict half_1st = array; unsigned *restrict half_2nd = array + n/2; // ... free( array ); }
Der Code könnte sicher auf der ersten und zweiten Hälfte des Arrays arbeiten, da sie sich nicht überlappen (vorausgesetzt, Sie greifen nie auf half_1st[n/2] oder darüber hinaus zu).
Drittens ist die Verwendung von „restrict“ in den Parametern einer Funktion möglicherweise weniger sicher. Vergleichen Sie beispielsweise „safer()“ mit „update_ptrs_v2()“, wobei der Aufrufer die Zeiger steuert. Soweit Sie wissen, hat der Anrufer es falsch verstanden und Hinweise auf diesen Alias übergeben.
Nur Zeiger auf Objekte (oder void) können mit „restrict:“ qualifiziert werden:
restrict int x; // error: can't restrict object int restrict *p; // error: pointer to restrict object int (*restrict f)(); // error: pointer-to-function
Sie können „restrict“ für Strukturmitglieder verwenden, zum Beispiel:
struct node { void *restrict data; struct node *restrict left; struct node *restrict right; };
besagt, dass Daten der einzige Zeiger auf diese Daten sind und dass links und rechts niemals auf denselben Knoten zeigen. Die Verwendung von „restrict“ für Strukturmitglieder ist jedoch sehr ungewöhnlich.
Schließlich verfügt C++ nicht über eine Einschränkung. Warum nicht? Es gibt eine lange Antwort, aber die TL;DR-Version lautet:
Viele Compiler haben jedoch __restrict__ als Erweiterung.
In begrenzten Fällen kann die Verwendung von „Restrict“ zu Leistungsverbesserungen führen, es gibt jedoch mehrere erhebliche Fallstricke. Wenn Sie die Verwendung von „Restrict“ in Betracht ziehen, profilieren Sie zuerst Ihren Code.
Mit Bedacht verwenden.
Das obige ist der detaillierte Inhalt vonDas obskure Schlüsselwort „restrict' in C. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!