restrict type qualifier (since C99)
Chaque type individuel dans le
système de types
du C possède plusieurs versions
qualifiées
de ce type, correspondant à un, deux ou aux trois
const
,
volatile
, et, pour les pointeurs vers des types objet,
restrict
qualifiers. Cette page décrit les effets du qualificateur
restrict
.
Seul un pointeur vers un type objet ou un tableau (éventuellement multidimensionnel) de celui-ci (depuis C23) peut être qualifié restrict ; en particulier, les cas suivants sont erronés :
- int restrict * p
- float ( * restrict f9 ) ( void )
La sémantique restrict s'applique uniquement aux expressions lvalue ; par exemple, un cast vers un pointeur qualifié restrict ou un appel de fonction retournant un pointeur qualifié restrict ne sont pas des lvalues et le qualificatif n'a aucun effet.
Durant chaque exécution d'un bloc dans lequel un pointeur restreint
P
est déclaré (typiquement chaque exécution d'un corps de fonction dans lequel
P
est un paramètre de fonction), si un objet accessible via
P
(directement ou indirectement) est modifié, par quelque moyen que ce soit, alors tous les accès à cet objet (lectures et écritures) dans ce bloc doivent s'effectuer via
P
(directement ou indirectement), sinon le comportement est indéfini :
void f(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; // aucun des objets modifié via *p n'est le même // qu'aucun des objets lu via *q // le compilateur est libre d'optimiser, vectoriser, mapper les pages, etc. } void g(void) { extern int d[100]; f(50, d + 50, d); // OK f(50, d + 1, d); // Comportement indéfini : d[1] est accédé à la fois via p et q dans f }
Si l'objet n'est jamais modifié, il peut être aliasé et accessible via différents pointeurs qualifiés restrict (notez que si les objets pointés par des pointeurs restrict qualifiés aliasés sont, à leur tour, des pointeurs, cet aliasing peut inhiber l'optimisation).
L'assignation d'un pointeur restreint à un autre est un comportement indéfini, sauf lors de l'assignation d'un pointeur vers un objet dans un bloc extérieur à un pointeur dans un bloc intérieur (y compris l'utilisation d'un argument de pointeur restreint lors de l'appel d'une fonction avec un paramètre de pointeur restreint) ou lors du retour d'une fonction (et autrement lorsque le bloc du pointeur source s'est terminé) :
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // comportement indéfini
Les pointeurs restreints peuvent être assignés librement à des pointeurs non restreints, les opportunités d'optimisation restent en place tant que le compilateur est capable d'analyser le code :
void f(int n, float * restrict r, float * restrict s) { float *p = r, *q = s; // OK while (n-- > 0) *p++ = *q++; // optimisé presque certainement comme *r++ = *s++ }
|
Si un type tableau est déclaré avec le qualificatif de type restrict (via l'utilisation de typedef ), le type tableau n'est pas qualifié restrict, mais son type d'élément l'est : |
(jusqu'à C23) |
|
Un type tableau et son type d'élément sont toujours considérés comme étant identiquement qualifiés restrict : |
(depuis C23) |
typedef int *array_t[10]; restrict array_t a; // le type de a est int *restrict[10] // Notes : clang et icc rejettent ceci au motif que array_t n'est pas un type pointeur void *unqual_ptr = &a; // OK jusqu'à C23 ; erreur depuis C23 // Notes : clang applique la règle de C++/C23 même dans les modes C89-C17
Dans une déclaration de fonction, le mot-clé
restrict
peut apparaître à l'intérieur des crochets utilisés pour déclarer un type tableau d'un paramètre de fonction. Il qualifie le type pointeur vers lequel le type tableau est transformé :
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); // comportement potentiellement indéfini (selon ce que fait f) }
Table des matières |
Notes
L'utilisation prévue du qualificatif restrict (comme la classe de stockage register) est de promouvoir l'optimisation, et supprimer toutes les instances du qualificatif de toutes les unités de traduction de prétraitement composant un programme conforme ne modifie pas sa signification (c'est-à-dire, le comportement observable).
Le compilateur est libre d'ignorer tout ou partie des implications d'aliasing des utilisations de
restrict
.
Pour éviter un comportement indéfini, le programmeur doit s'assurer que les assertions d'aliasing faites par les pointeurs qualifiés restrict ne sont pas violées.
De nombreux compilateurs fournissent, comme extension de langage, l'opposé de
restrict
: un attribut indiquant que les pointeurs peuvent être aliasés même si leurs types diffèrent :
may_alias
(gcc),
Modèles d'utilisation
Il existe plusieurs modèles d'utilisation courants pour les pointeurs qualifiés restrict :
Portée de fichier
Un pointeur qualifié restrict de portée de fichier doit pointer vers un seul objet tableau pour la durée du programme. Cet objet tableau ne peut pas être référencé à la fois via le pointeur restrict et via soit son nom déclaré (s'il en a un) soit un autre pointeur restrict.
Les pointeurs restreints à la portée du fichier sont utiles pour fournir un accès aux tableaux globaux alloués dynamiquement ; la sémantique restrict permet d'optimiser les références via ce pointeur aussi efficacement que les références à un tableau statique via son nom déclaré :
float *restrict a, *restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; // a fait référence à la première moitié b = t + n; // b fait référence à la seconde moitié } // le compilateur peut déduire des qualificateurs restrict qu' // il n'y a pas d'aliasing potentiel entre les noms a, b et c
Paramètre de fonction
Le cas d'utilisation le plus populaire pour les pointeurs qualifiés restrict est leur utilisation comme paramètres de fonction.
Dans l'exemple suivant, le compilateur peut déduire qu'il n'y a pas d'alias des objets modifiés, et ainsi optimiser la boucle de manière agressive.
À l'entrée de
f
, le pointeur restreint a doit fournir un accès exclusif à son tableau associé. En particulier, dans
f
, ni
b
ni
c
ne peuvent pointer vers le tableau associé à
a
, car aucun n'est assigné d'une valeur de pointeur basée sur
a
. Pour
b
, cela est évident d'après le qualificatif const dans sa déclaration, mais pour
c
, une inspection du corps de
f
est nécessaire :
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); // Correct f( 50, d, d+50); // Correct f( 99, d+1, d); // comportement indéfini c = d; f( 99, d+1, e); // comportement indéfini f( 99, e, d+1); // Correct }
Notez qu'il est permis que c pointe vers le tableau associé à b. Notez également que, pour ces fins, le "tableau" associé à un pointeur particulier signifie uniquement la portion d'un objet tableau qui est effectivement référencée par ce pointeur.
Notez que dans l'exemple ci-dessus, le compilateur peut déduire que a et b ne sont pas des alias car la constance de b garantit qu'il ne peut pas devenir dépendant de a dans le corps de la fonction. De manière équivalente, le programmeur pourrait écrire void f ( int n, float * a, float const * restrict b ) , auquel cas le compilateur peut raisonner que les objets référencés via b ne peuvent pas être modifiés, et donc aucun objet modifié ne peut être référencé à la fois par b et a. Si le programmeur écrivait void f ( int n, float * restrict a, float * b ) , le compilateur ne pourrait pas déduire la non-aliasing de a et b sans examiner le corps de la fonction.
En général, il est préférable d'annoter explicitement tous les pointeurs non-alias dans le prototype d'une fonction avec restrict .
Portée de bloc
Un pointeur qualifié restrict de portée de bloc effectue une assertion d'aliasing limitée à son bloc. Il permet des assertions locales qui s'appliquent uniquement à des blocs importants, tels que des boucles serrées. Il rend également possible la conversion d'une fonction qui prend des pointeurs qualifiés restrict en une macro :
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)
Membres de structure
La portée de l'assertion d'aliasing faite par un pointeur qualifié restrict qui est membre d'une struct est la portée de l'identifiant utilisé pour accéder à la struct.
Même si la structure est déclarée au niveau de la portée du fichier, lorsque l'identifiant utilisé pour accéder à la structure a une portée de bloc, les assertions d'aliasing dans la structure ont également une portée de bloc ; les assertions d'aliasing ne sont effectives que pendant l'exécution d'un bloc ou un appel de fonction, selon la manière dont l'objet de ce type de structure a été créé :
struct t // Les pointeurs restrict indiquent que { int n; // les membres pointent vers des zones mémoire disjointes. float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r,s,u ont une portée de bloc // r.p, r.q, s.p, s.q, u.p, u.q doivent tous pointer vers // des zones mémoire disjointes durant chaque exécution de ff. // ... }
Mots-clés
Exemple
exemple de génération de code ; compiler avec -S (gcc, clang, etc) ou /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; }
Sortie possible :
; code généré sur plateforme Intel 64 bits : foo: movl $5, (%rdi) ; stocker 5 dans *a movl $6, (%rsi) ; stocker 6 dans *b movl (%rdi), %eax ; relire depuis *a au cas où l'écriture précédente l'aurait modifié addl $6, %eax ; ajouter 6 à la valeur lue depuis *a ret rfoo: movl $11, %eax ; le résultat est 11, une constante à la compilation movl $5, (%rdi) ; stocker 5 dans *a movl $6, (%rsi) ; stocker 6 dans *b ret
Références
- Norme C23 (ISO/IEC 9899:2024) :
-
- 6.7.3.1 Définition formelle de restrict (p: TBD)
- Norme C17 (ISO/CEI 9899:2018) :
-
- 6.7.3.1 Définition formelle de restrict (p : 89-90)
- Norme C11 (ISO/IEC 9899:2011) :
-
- 6.7.3.1 Définition formelle de restrict (p: 123-125)
- Norme C99 (ISO/IEC 9899:1999) :
-
- 6.7.3.1 Définition formelle de restrict (p: 110-112)