restrict type qualifier (since C99)
Jeder individuelle Typ im C
Typsystem
hat mehrere
qualifizierte
Versionen dieses Typs, entsprechend einem, zwei oder allen drei der
const
,
volatile
, und, für Zeiger auf Objekttypen,
restrict
Qualifizierer. Diese Seite beschreibt die Effekte des
restrict
Qualifizierers.
Nur ein Zeiger auf einen Objekttyp oder einen (möglicherweise mehrdimensionalen) Array davon (seit C23) darf restrict-qualifiziert sein; insbesondere sind folgende fehlerhaft :
- int restrict * p
- float ( * restrict f9 ) ( void )
Restrict-Semantik gilt nur für Lvalue-Ausdrücke; zum Beispiel sind eine Umwandlung in einen restrict-qualifizierten Zeiger oder ein Funktionsaufruf, der einen restrict-qualifizierten Zeiger zurückgibt, keine Lvalues und der Qualifizierer hat keine Wirkung.
Während jeder Ausführung eines Blocks, in dem ein eingeschränkter Zeiger
P
deklariert ist (typischerweise jede Ausführung eines Funktionsrumpfs, in dem
P
ein Funktionsparameter ist), wenn ein Objekt, das durch
P
zugänglich ist (direkt oder indirekt), auf irgendeine Weise modifiziert wird, dann müssen alle Zugriffe auf dieses Objekt (sowohl Lese- als auch Schreibzugriffe) in diesem Block durch
P
erfolgen (direkt oder indirekt), andernfalls ist das Verhalten undefiniert:
void f(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; // keines der durch *p modifizierten Objekte ist dasselbe // wie eines der durch *q gelesenen Objekte // Compiler kann optimieren, vektorisieren, Seitenabbildung durchführen, etc. } void g(void) { extern int d[100]; f(50, d + 50, d); // OK f(50, d + 1, d); // Undefiniertes Verhalten: d[1] wird in f sowohl durch p als auch durch q zugegriffen }
Wenn das Objekt niemals modifiziert wird, kann es gealiast und über verschiedene restrict-qualifizierte Zeiger zugegriffen werden (beachten Sie, dass, wenn die durch gealiaste restrict-qualifizierte Zeiger referenzierten Objekte selbst Zeiger sind, dieses Aliasing die Optimierung beeinträchtigen kann).
Die Zuweisung von einem eingeschränkten Zeiger zu einem anderen ist undefiniertes Verhalten, außer wenn von einem Zeiger auf ein Objekt in einem äußeren Block zu einem Zeiger in einem inneren Block zugewiesen wird (einschließlich der Verwendung eines eingeschränkten Zeigerarguments beim Aufruf einer Funktion mit einem eingeschränkten Zeigerparameter) oder bei der Rückkehr aus einer Funktion (und anderweitig, wenn der Block des Ursprungszeigers endete):
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // undefiniertes Verhalten
Eingeschränkte Zeiger können frei nicht eingeschränkten Zeigern zugewiesen werden, die Optimierungsmöglichkeiten bleiben bestehen, solange der Compiler in der Lage ist, den Code zu analysieren:
void f(int n, float * restrict r, float * restrict s) { float *p = r, *q = s; // OK while (n-- > 0) *p++ = *q++; // höchstwahrscheinlich genauso optimiert wie *r++ = *s++ }
|
Wenn ein Array-Typ mit dem restrict-Typspezifizierer deklariert wird (durch die Verwendung von typedef ), ist der Array-Typ nicht restrict-qualifiziert, aber sein Elementtyp ist es: |
(until C23) |
|
Ein Array-Typ und sein Elementtyp werden immer als identisch restrict-qualifiziert betrachtet: |
(since C23) |
typedef int *array_t[10]; restrict array_t a; // der Typ von a ist int *restrict[10] // Hinweis: clang und icc lehnen dies ab, da array_t kein Zeigertyp ist void *unqual_ptr = &a; // OK bis C23; Fehler seit C23 // Hinweis: clang wendet die Regel aus C++/C23 bereits in C89-C17-Modi an
In einer Funktionsdeklaration kann das Schlüsselwort
restrict
innerhalb der eckigen Klammern erscheinen, die zur Deklaration eines Array-Typs eines Funktionsparameters verwendet werden. Es qualifiziert den Zeigertyp, in den der Array-Typ transformiert wird:
void f(int m, int n, float a[restrict m][n], float b[restrict m][n]); void g12(int n, float (*p)[n]) { f(10, n, p, p+10); // OK f(20, n, p, p+10); // mögliches undefiniertes Verhalten (abhängig davon, was f tut) }
Inhaltsverzeichnis |
Hinweise
Die beabsichtigte Verwendung des restrict-Qualifizierers (ähnlich der register-Speicherklasse) besteht darin, die Optimierung zu fördern, und das Löschen aller Instanzen des Qualifizierers aus allen Präprozessor-Übersetzungseinheiten, die ein konformes Programm bilden, ändert nicht dessen Bedeutung (d.h., das beobachtbare Verhalten).
Dem Compiler steht es frei, alle oder einen Teil der Aliasing-Implikationen von Verwendungen von
restrict
zu ignorieren.
Um undefiniertes Verhalten zu vermeiden, muss der Programmierer sicherstellen, dass die durch restrict-qualifizierte Zeiger getroffenen Aliasing-Annahmen nicht verletzt werden.
Viele Compiler bieten als Spracherweiterung das Gegenteil von
restrict
: ein Attribut, das anzeigt, dass Zeiger sich überschneiden können, selbst wenn ihre Typen unterschiedlich sind:
may_alias
(gcc),
Verwendungsmuster
Es gibt mehrere häufige Verwendungsmuster für restrict-qualifizierte Zeiger:
Dateibereich
Ein dateiweiter restrict-qualifizierter Zeiger muss für die Dauer des Programms auf ein einzelnes Array-Objekt zeigen. Dieses Array-Objekt darf nicht sowohl über den restrict-Zeiger als auch über entweder seinen deklarierten Namen (falls vorhanden) oder einen anderen restrict-Zeiger referenziert werden.
Dateibereichsbegrenzte Zeiger sind nützlich, um Zugriff auf dynamisch allokierte globale Arrays zu ermöglichen; die restrict-Semantik ermöglicht es, Referenzen über diesen Zeiger ebenso effektiv zu optimieren wie Referenzen auf ein statisches Array über seinen deklarierten Namen:
float *restrict a, *restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; // a verweist auf die erste Hälfte b = t + n; // b verweist auf die zweite Hälfte } // Der Compiler kann aus den restrict-Qualifizierern ableiten, dass // kein potenzielles Aliasing zwischen den Namen a, b und c besteht
Funktionsparameter
Der beliebteste Anwendungsfall für restrict-qualifizierte Zeiger ist die Verwendung als Funktionsparameter.
Im folgenden Beispiel könnte der Compiler ableiten, dass kein Aliasing modifizierter Objekte vorliegt, und die Schleife daher aggressiv optimieren.
Beim Eintritt in
f
muss der restricted-Zeiger a exklusiven Zugriff auf sein zugeordnetes Array gewährleisten. Insbesondere dürfen innerhalb von
f
weder
b
noch
c
auf das Array zeigen, das
a
zugeordnet ist, da keiner von ihnen einen Zeigerwert basierend auf
a
zugewiesen bekommt. Für
b
ergibt sich dies aus dem const-Qualifier in seiner Deklaration, aber für
c
ist eine Überprüfung des Rumpfes von
f
erforderlich:
float x[100]; float *c; void f(int n, float * restrict a, float * const b) { int i; for ( i=0; i<n; i++ ) a[i] = b[i] + c[i]; } void g3(void) { float d[100], e[100]; c = x; f(100, d, e); // OK f( 50, d, d+50); // OK f( 99, d+1, d); // undefiniertes Verhalten c = d; f( 99, d+1, e); // undefiniertes Verhalten f( 99, e, d+1); // OK }
Beachten Sie, dass es erlaubt ist, dass c in das Array zeigt, das mit b assoziiert ist. Beachten Sie auch, dass für diese Zwecke das "Array", das mit einem bestimmten Zeiger assoziiert ist, nur denjenigen Teil eines Array-Objekts bedeutet, der tatsächlich durch diesen Zeiger referenziert wird.
Beachten Sie, dass der Compiler im obigen Beispiel ableiten kann, dass a und b sich nicht überlappen, weil die Konstanz von b garantiert, dass es innerhalb des Funktionskörpers nicht von a abhängig werden kann. Äquivalent könnte der Programmierer schreiben void f ( int n, float * a, float const * restrict b ) , in diesem Fall kann der Compiler schlussfolgern, dass über b referenzierte Objekte nicht modifiziert werden können und somit kein modifiziertes Objekt sowohl über b als auch a referenziert werden kann. Wenn der Programmierer schreiben würde void f ( int n, float * restrict a, float * b ) , könnte der Compiler ohne Untersuchung des Funktionskörpers keine Nicht-Überlappung von a und b ableiten.
Im Allgemeinen ist es am besten, alle nicht-aliasing Zeiger in einem Funktionsprototyp explizit mit restrict zu annotieren.
Blockbereich
Ein blockbegrenzter restrict-qualifizierter Zeiger macht eine Aliasing-Behauptung, die auf seinen Block beschränkt ist. Es erlaubt lokale Behauptungen, die nur für wichtige Blöcke gelten, wie etwa enge Schleifen. Es macht auch möglich, eine Funktion, die restrict-qualifizierte Zeiger akzeptiert, in ein Makro umzuwandeln:
float x[100]; float *c; #define f3(N, A, B) \ do \ { int n = (N); \ float * restrict a = (A); \ float * const b = (B); \ int i; \ for ( i=0; i<n; i++ ) \ a[i] = b[i] + c[i]; \ } while(0)
Strukturelemente
Der Geltungsbereich der Aliasing-Behauptung, die durch einen restrict-qualifizierten Zeiger gemacht wird, der ein Mitglied einer Struktur ist, ist der Geltungsbereich des Bezeichners, der für den Zugriff auf die Struktur verwendet wird.
Selbst wenn die Struktur auf Dateigültigkeitsbereich deklariert ist, wenn der Bezeichner zum Zugriff auf die Struktur Blockgültigkeitsbereich hat, haben auch die Aliasing-Assertions in der Struktur Blockgültigkeitsbereich; die Aliasing-Assertions sind nur innerhalb eines Blockausführungsbereichs oder eines Funktionsaufrufs wirksam, abhängig davon, wie das Objekt dieses Strukturtyps erstellt wurde:
struct t // Restricted Pointer stellen sicher, dass { int n; // Member auf getrennte Speicherbereiche zeigen. float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r,s,u haben Blockgültigkeit // r.p, r.q, s.p, s.q, u.p, u.q sollten alle auf // getrennte Speicherbereiche während jeder Ausführung von ff zeigen. // ... }
Schlüsselwörter
Beispiel
Code-Generierungsbeispiel; kompilieren mit -S (gcc, clang, etc.) oder /FA (Visual Studio)
int foo(int *a, int *b) { *a = 5; *b = 6; return *a + *b; } int rfoo(int *restrict a, int *restrict b) { *a = 5; *b = 6; return *a + *b; }
Mögliche Ausgabe:
; generierter Code auf 64-Bit-Intel-Plattform: foo: movl $5, (%rdi) ; speichere 5 in *a movl $6, (%rsi) ; speichere 6 in *b movl (%rdi), %eax ; lese erneut von *a falls vorherige Speicheroperation es verändert hat addl $6, %eax ; addiere 6 zum von *a gelesenen Wert ret rfoo: movl $11, %eax ; das Ergebnis ist 11, eine Kompilierzeitkonstante movl $5, (%rdi) ; speichere 5 in *a movl $6, (%rsi) ; speichere 6 in *b ret
Referenzen
- C23-Standard (ISO/IEC 9899:2024):
-
- 6.7.3.1 Formale Definition von restrict (S.: TBD)
- C17-Standard (ISO/IEC 9899:2018):
-
- 6.7.3.1 Formale Definition von restrict (S: 89-90)
- C11-Standard (ISO/IEC 9899:2011):
-
- 6.7.3.1 Formale Definition von restrict (S: 123-125)
- C99-Standard (ISO/IEC 9899:1999):
-
- 6.7.3.1 Formale Definition von restrict (S. 110-112)