Namensräume
Varianten
Aktionen

restrict Typqualifizierer (seit C99)

Von cppreference.com
< c‎ | Sprache

Jeder einzelne Typ im C Typsystem hat mehrere qualifizierte Versionen dieses Typs, die einer, zweien oder allen drei der const, volatile und, bei Zeigern auf Objekttypen, restrict Qualifizierer entsprechen. Diese Seite beschreibt die Auswirkungen des restrict Qualifizierers.

Nur ein Zeiger auf einen Objekttyp oder ein (möglicherweise mehrdimensionaler) Array davon(seit C23) darf restrict-qualifiziert sein; insbesondere sind die folgenden fehlerhaft

  • int restrict *p
  • float (* restrict f9)(void)

Restrict-Semantik gilt nur für L-Wert-Ausdrücke; beispielsweise sind eine Umwandlung in einen restrict-qualifizierten Zeiger oder ein Funktionsaufruf, der einen restrict-qualifizierten Zeiger zurückgibt, keine L-Werte und der Qualifizierer hat keine Auswirkung.

Während jeder Ausführung eines Blocks, in dem ein beschränkter Zeiger P deklariert ist (typischerweise jede Ausführung eines Funktionskörpers, in dem P ein Funktionsparameter ist), wenn ein Objekt, auf das durch P zugegriffen werden kann (direkt oder indirekt), auf irgendeine Weise modifiziert wird, müssen alle Zugriffe auf dieses Objekt (sowohl Lese- als auch Schreibzugriffe) in diesem Block über P (direkt oder indirekt) erfolgen, andernfalls ist das Verhalten undefiniert.

void f(int n, int * restrict p, int * restrict q)
{
    while (n-- > 0)
        *p++ = *q++; // none of the objects modified through *p is the same
                     // as any of the objects read through *q
                     // compiler free to optimize, vectorize, page map, etc.
}
 
void g(void)
{
    extern int d[100];
    f(50, d + 50, d); // OK
    f(50, d + 1, d);  // Undefined behavior: d[1] is accessed through both p and q in f
}

Wenn das Objekt nie modifiziert wird, kann es aliased sein und über verschiedene restrict-qualifizierte Zeiger darauf zugegriffen werden (beachten Sie, dass, wenn die von den aliased restrict-qualifizierten Zeigern referenzierten Objekte ihrerseits Zeiger sind, dieses Aliasing die Optimierung behindern kann).

Zuweisung von einem beschränkten Zeiger zu einem anderen ist undefiniertes Verhalten, außer beim Zuweisen von einem Zeiger auf ein Objekt in einem äußeren Block zu einem Zeiger in einem inneren Block (einschließlich der Verwendung eines beschränkten Zeigerarguments beim Aufruf einer Funktion mit einem beschränkten Zeigerparameters) oder beim Zurückkehren aus einer Funktion (und ansonsten, wenn der Block des Quellzeigers beendet wurde).

int* restrict p1 = &a;
int* restrict p2 = &b;
p1 = p2; // undefined behavior

Beschränkte Zeiger können frei auf unbeschränkte Zeiger zugewiesen werden; die Optimierungsmöglichkeiten bleiben erhalten, solange der Compiler den Code analysieren kann.

void f(int n, float * restrict r, float * restrict s)
{
    float *p = r, *q = s; // OK
    while (n-- > 0)
        *p++ = *q++; // almost certainly optimized just like *r++ = *s++
}

Wenn ein Array-Typ mit dem restrict-Typqualifizierer deklariert wird (durch die Verwendung von typedef), ist der Array-Typ nicht restrict-qualifiziert, aber sein Elementtyp ist es.

(bis C23)

Ein Array-Typ und sein Elementtyp werden immer als identisch restrict-qualifiziert betrachtet.

(seit C23)
typedef int *array_t[10];
 
restrict array_t a; // the type of a is int *restrict[10]
// Notes: clang and icc reject this on the grounds that array_t is not a pointer type
 
void *unqual_ptr = &a; // OK until C23; error since C23
// Notes: clang applies the rule in C++/C23 even in C89-C17 modes

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, zu dem 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); // possibly undefined behavior (depending on what f does)
}

Inhalt

[bearbeiten] Hinweise

Der beabsichtigte Verwendungszweck des restrict-Qualifizierers (wie die register-Speicherklasse) ist die Förderung der Optimierung, und das Löschen aller Instanzen des Qualifizierers aus allen Präprozessor-Übersetzungseinheiten, die ein konformes Programm bilden, ändert dessen Bedeutung (d.h. beobachtbares Verhalten) nicht.

Der Compiler kann die Aliasing-Implikationen von Verwendungen von restrict frei ignorieren.

Um undefiniertes Verhalten zu vermeiden, muss der Programmierer sicherstellen, dass die durch die restrict-qualifizierten Zeiger gemachten Aliasing-Aussagen nicht verletzt werden.

Viele Compiler bieten als Spracherweiterung das Gegenteil von restrict: ein Attribut, das angibt, dass Zeiger sich überlappen können, auch wenn ihre Typen unterschiedlich sind: may_alias (gcc).

[bearbeiten] Verwendungsmuster

Es gibt mehrere gängige Verwendungsmuster für restrict-qualifizierte Zeiger.

[bearbeiten] Dateibereich

Ein restrict-qualifizierter Zeiger im Dateibereich muss während der gesamten Programmausführung auf ein einzelnes Array-Objekt zeigen. Dieses Array-Objekt darf weder durch den beschränkten Zeiger noch durch seinen deklarierten Namen (falls vorhanden) oder einen anderen beschränkten Zeiger referenziert werden.

Beschränkte Zeiger im Dateibereich sind nützlich für den Zugriff auf dynamisch zugewiesene globale Arrays; die restrict-Semantik ermöglicht die Optimierung von Referenzen über diesen Zeiger genauso effektiv wie bei 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 refers to 1st half
   b = t + n;  // b refers to 2nd half
}
// compiler can deduce from the restrict qualifiers that
// there is no potential aliasing among the names a, b, and c

[bearbeiten] Funktionsparameter

Der beliebteste Anwendungsfall für restrict-qualifizierte Zeiger ist die Verwendung als Funktionsparameter.

Im folgenden Beispiel kann der Compiler ableiten, dass keine Aliasing von modifizierten Objekten stattfindet, und die Schleife daher aggressiv optimieren. Beim Eintritt in f muss der beschränkte Zeiger a exklusiven Zugriff auf sein zugehöriges Array bieten. Insbesondere dürfen weder b noch c innerhalb von f in das Array zeigen, das mit a assoziiert ist, da keiner von ihnen einen auf a basierenden Zeigerwert zugewiesen bekommt. Für b ist dies aufgrund des const-Qualifizierers in seiner Deklaration offensichtlich, aber für c ist eine Untersuchung des Körpers 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); // undefined behavior
    c = d; f( 99, d+1,    e); // undefined behavior
           f( 99,   e,  d+1); // OK
}

Beachten Sie, dass es zulässig 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 den Teil eines Array-Objekts bedeutet, der tatsächlich über diesen Zeiger referenziert wird.

Beachten Sie, dass im obigen Beispiel der Compiler ableiten kann, dass a und b sich nicht überlappen, da die Konstheit von b garantiert, dass es im Funktionskörper nicht von a abhängen kann. Äquivalent könnte der Programmierer void f(int n, float * a, float const * restrict b) schreiben, in diesem Fall kann der Compiler feststellen, dass Objekte, auf die über b zugegriffen wird, nicht modifiziert werden können, und daher kein modifiziertes Objekt sowohl über b als auch über a referenziert werden kann. Wenn der Programmierer void f(int n, float * restrict a, float * b) schreiben würde, wäre der Compiler nicht in der Lage, Nicht-Aliasing von a und b abzuleiten, ohne den Funktionskörper zu untersuchen.

Im Allgemeinen ist es am besten, alle nicht-aliasing-Zeiger in der Prototyp einer Funktion explizit mit restrict zu kennzeichnen.

[bearbeiten] Blockbereich

Ein restrict-qualifizierter Zeiger im Blockbereich macht eine Aliasing-Aussage, die auf seinen Block beschränkt ist. Er ermöglicht lokale Aussagen, die nur für wichtige Blöcke gelten, wie z.B. enge Schleifen. Er ermöglicht auch die Umwandlung einer Funktion, die restrict-qualifizierte Zeiger annimmt, in ein Makro.

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)

[bearbeiten] Strukturmember

Der Bereich der Aliasing-Aussage eines restrict-qualifizierten Zeigers, der ein Member einer Struktur ist, ist der Bereich des Identifikators, der zum Zugriff auf die Struktur verwendet wird.

Auch wenn die Struktur im Dateibereich deklariert ist, haben die Aliasing-Aussagen in der Struktur Blockbereich, wenn der Identifikator, der zum Zugriff auf die Struktur verwendet wird, Blockbereich hat; die Aliasing-Aussagen sind nur innerhalb einer Blockausführung oder eines Funktionsaufrufs wirksam, je nachdem, wie das Objekt dieses Strukturtyps erstellt wurde.

struct t      // Restricted pointers assert that
{
   int n;     // members point to disjoint storage.
   float * restrict p;
   float * restrict q;
};
 
void ff(struct t r, struct t s)
{
   struct t u;
   // r,s,u have block scope
   // r.p, r.q, s.p, s.q, u.p, u.q should all point to
   // disjoint storage during each execution of ff.
   // ...
}

[bearbeiten] Schlüsselwörter

restrict

[bearbeiten] 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

; generated code on 64bit Intel platform:
foo:
    movl    $5, (%rdi)    ; store 5 in *a
    movl    $6, (%rsi)    ; store 6 in *b
    movl    (%rdi), %eax  ; read back from *a in case previous store modified it
    addl    $6, %eax      ; add 6 to the value read from *a
    ret
 
rfoo:
    movl      $11, %eax   ; the result is 11, a compile-time constant
    movl      $5, (%rdi)  ; store 5 in *a
    movl      $6, (%rsi)  ; store 6 in *b
    ret

[bearbeiten] 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)