restrict type qualifier (since C99)
Cada tipo individual en el
sistema de tipos
de C tiene varias versiones
calificadas
de ese tipo, correspondientes a uno, dos, o los tres calificadores:
const
,
volatile
, y, para punteros a tipos objeto,
restrict
. Esta página describe los efectos del calificador
restrict
.
Solo un puntero a un object type o un array (posiblemente multidimensional) del mismo (since C23) puede ser restrict-qualified; en particular, lo siguiente es erroneous :
- int restrict * p
- float ( * restrict f9 ) ( void )
Las semánticas restrict se aplican únicamente a expresiones lvalue; por ejemplo, una conversión a un puntero calificado con restrict o una llamada a función que devuelve un puntero calificado con restrict no son lvalues y el calificador no tiene efecto.
Durante cada ejecución de un bloque en el que se declara un puntero restringido
P
(típicamente cada ejecución del cuerpo de una función en el que
P
es un parámetro de función), si algún objeto que es accesible a través de
P
(directa o indirectamente) es modificado, por cualquier medio, entonces todos los accesos a ese objeto (tanto lecturas como escrituras) en ese bloque deben ocurrir a través de
P
(directa o indirectamente), de lo contrario el comportamiento es indefinido:
void f(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; // ninguno de los objetos modificados a través de *p es el mismo // que cualquiera de los objetos leídos a través de *q // el compilador puede optimizar, vectorizar, mapear páginas, etc. } void g(void) { extern int d[100]; f(50, d + 50, d); // CORRECTO f(50, d + 1, d); // Comportamiento indefinido: d[1] se accede tanto por p como por q en f }
Si el objeto nunca se modifica, puede tener alias y ser accedido a través de diferentes punteros calificados con restrict (nótese que si los objetos apuntados por punteros calificados con restrict con alias son, a su vez, punteros, este aliasing puede inhibir la optimización).
La asignación de un puntero restringido a otro es comportamiento indefinido, excepto cuando se asigna desde un puntero a un objeto en algún bloque externo a un puntero en algún bloque interno (incluyendo el uso de un argumento de puntero restringido al llamar a una función con un parámetro de puntero restringido) o cuando se retorna de una función (y en otros casos cuando el bloque del puntero de origen ha terminado):
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // comportamiento indefinido
Los punteros restringidos pueden asignarse a punteros no restringidos libremente, las oportunidades de optimización permanecen vigentes siempre que el compilador sea capaz de analizar el código:
void f(int n, float * restrict r, float * restrict s) { float *p = r, *q = s; // CORRECTO while (n-- > 0) *p++ = *q++; // casi con certeza optimizado igual que *r++ = *s++ }
|
Si un tipo de array se declara con el calificador de tipo restrict (mediante el uso de typedef ), el tipo de array no está calificado como restrict, pero su tipo de elemento sí lo está: |
(until C23) |
|
Un tipo de array y su tipo de elemento siempre se consideran idénticamente calificados como restrict: |
(since C23) |
typedef int *array_t[10]; restrict array_t a; // el tipo de a es int *restrict[10] // Notas: clang e icc rechazan esto basándose en que array_t no es un tipo puntero void *unqual_ptr = &a; // Válido hasta C23; error desde C23 // Notas: clang aplica la regla de C++/C23 incluso en modos C89-C17
En una declaración de función, la palabra clave
restrict
puede aparecer dentro de los corchetes que se utilizan para declarar un tipo de array de un parámetro de función. Califica el tipo de puntero al cual se transforma el tipo de array:
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); // CORRECTO f(20, n, p, p+10); // posible comportamiento indefinido (dependiendo de lo que haga f) }
Contenidos |
Notas
El uso previsto del calificador restrict (como la clase de almacenamiento register) es promover la optimización, y eliminar todas las instancias del calificador de todas las unidades de traducción de preprocesamiento que componen un programa conforme no cambia su significado (es decir, comportamiento observable).
El compilador tiene libertad para ignorar cualquier o todas las implicaciones de alias de los usos de
restrict
.
Para evitar comportamiento indefinido, el programador debe asegurarse de que las aserciones de aliasing realizadas por los punteros calificados con restrict no sean violadas.
Muchos compiladores proporcionan, como una extensión del lenguaje, lo opuesto a
restrict
: un atributo que indica que los punteros pueden hacer aliasing incluso si sus tipos difieren:
may_alias
(gcc),
Patrones de uso
Existen varios patrones de uso comunes para los punteros calificados con restrict:
Ámbito de archivo
Un puntero calificado con restrict a nivel de archivo debe apuntar a un único objeto array durante toda la duración del programa. Ese objeto array no puede ser referenciado tanto a través del puntero restringido como a través de su nombre declarado (si tiene uno) u otro puntero restringido.
Los punteros restringidos al ámbito de archivo son útiles para proporcionar acceso a arreglos globales asignados dinámicamente; la semántica restrict hace posible optimizar las referencias a través de este puntero tan efectivamente como las referencias a un arreglo estático a través de su nombre declarado:
float *restrict a, *restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; // a hace referencia a la primera mitad b = t + n; // b hace referencia a la segunda mitad } // el compilador puede deducir a partir de los calificadores restrict que // no hay aliasing potencial entre los nombres a, b y c
Parámetro de función
El caso de uso más popular para los punteros calificados con restrict es su uso como parámetros de función.
En el siguiente ejemplo, el compilador puede inferir que no hay aliasing de objetos modificados, y por lo tanto optimizar el bucle de manera agresiva.
Al entrar en
f
, el puntero restringido a debe proporcionar acceso exclusivo a su array asociado. En particular, dentro de
f
ni
b
ni
c
pueden apuntar al array asociado con
a
, porque ninguno está asignado con un valor de puntero basado en
a
. Para
b
, esto es evidente por el calificador const en su declaración, pero para
c
, se requiere una inspección del cuerpo de
f
:
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); // CORRECTO f( 50, d, d+50); // CORRECTO f( 99, d+1, d); // comportamiento indefinido c = d; f( 99, d+1, e); // comportamiento indefinido f( 99, e, d+1); // CORRECTO }
Tenga en cuenta que está permitido que c apunte al array asociado con b. Tenga en cuenta también que, para estos propósitos, el "array" asociado con un puntero particular significa solo aquella porción de un objeto array que realmente se referencia a través de ese puntero.
Nótese que en el ejemplo anterior, el compilador puede inferir que a y b no son alias porque la constancia de b garantiza que no puede volverse dependiente de a en el cuerpo de la función. Equivalentemente, el programador podría escribir void f ( int n, float * a, float const * restrict b ) , en cuyo caso el compilador puede razonar que los objetos referenciados a través de b no pueden ser modificados, y por lo tanto ningún objeto modificado puede ser referenciado usando tanto b como a. Si el programador escribiera void f ( int n, float * restrict a, float * b ) , el compilador no podría inferir la no-aliasing de a y b sin examinar el cuerpo de la función.
En general, es mejor anotar explícitamente todos los punteros que no generan alias en el prototipo de una función con restrict .
Ámbito de bloque
Un puntero calificado con restrict en ámbito de bloque hace una aserción de aliasing que está limitada a su bloque. Permite aserciones locales que se aplican solo a bloques importantes, como bucles ajustados. También hace posible convertir una función que toma punteros calificados con restrict en una 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)
Miembros de estructura
El alcance de la aserción de aliasing realizada por un puntero calificado con restrict que es miembro de una estructura es el alcance del identificador utilizado para acceder a la estructura.
Aunque la estructura se declare en el ámbito de archivo, cuando el identificador utilizado para acceder a la estructura tiene ámbito de bloque, las aserciones de aliasing en la estructura también tienen ámbito de bloque; las aserciones de aliasing solo están en efecto dentro de una ejecución de bloque o una llamada a función, dependiendo de cómo se creó el objeto de este tipo de estructura:
struct t // Los punteros restrict afirman que { int n; // los miembros apuntan a almacenamiento disjunto. float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r,s,u tienen ámbito de bloque // r.p, r.q, s.p, s.q, u.p, u.q deben apuntar todos a // almacenamiento disjunto durante cada ejecución de ff. // ... }
Palabras clave
Ejemplo
ejemplo de generación de código; compile con -S (gcc, clang, etc) o /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; }
Salida posible:
; código generado en plataforma Intel de 64 bits: foo: movl $5, (%rdi) ; almacenar 5 en *a movl $6, (%rsi) ; almacenar 6 en *b movl (%rdi), %eax ; leer nuevamente desde *a en caso de que el almacenamiento previo lo modificara addl $6, %eax ; sumar 6 al valor leído desde *a ret rfoo: movl $11, %eax ; el resultado es 11, una constante en tiempo de compilación movl $5, (%rdi) ; almacenar 5 en *a movl $6, (%rsi) ; almacenar 6 en *b ret
Referencias
- Estándar C23 (ISO/IEC 9899:2024):
-
- 6.7.3.1 Definición formal de restrict (p: TBD)
- Estándar C17 (ISO/IEC 9899:2018):
-
- 6.7.3.1 Definición formal de restrict (p: 89-90)
- Estándar C11 (ISO/IEC 9899:2011):
-
- 6.7.3.1 Definición formal de restrict (p: 123-125)
- Estándar C99 (ISO/IEC 9899:1999):
-
- 6.7.3.1 Definición formal de restrict (p: 110-112)