Spec-Zone.ru › C

Квалификатор типа restrict (с C99)

Каждый отдельный тип в системе типов C имеет несколько квалифицированных версий этого типа, соответствующих одному, двум или всем трём из const, volatile и, для указателей на типы объектов, restrict квалификаторов. На этой странице описаны эффекты квалификатора restrict.

Только указатель на тип объекта или (возможно, многомерный) массив от него(с C23) может быть квалифицирован как restrict; в частности, следующие являются ошибочными:

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

Семантика restrict применяется только к выражениям с lvalue; например, приведение к указателю с квалификатором restrict или вызов функции, возвращающей указатель с квалификатором restrict, не являются lvalue, и квалификатор не оказывает влияния.

Во время каждого выполнения блока, в котором объявлен ограниченный указатель P (обычно каждое выполнение тела функции, в которой P является параметром функции), если какой-либо объект, доступный через P (прямо или косвенно), модифицируется любым способом, то все обращения к этому объекту (как чтения, так и записи) в этом блоке должны происходить через P (прямо или косвенно), в противном случае поведение является неопределённым:

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
}

Если объект никогда не изменяется, он может быть алиасом и доступен через разные ограниченные указатели (обратите внимание, что если объекты, на которые указывают алиасированные ограниченные указатели, в свою очередь, являются указателями, это алиасирование может препятствовать оптимизации).

Присваивание от одного ограниченного указателя к другому является неопределённым поведением, за исключением случаев присваивания от указателя на объект в некотором внешнем блоке к указателю в некотором внутреннем блоке (включая использование ограниченного указателя в качестве аргумента при вызове функции с ограниченным указателем в качестве параметра) или при возвращении из функции (и в иных случаях, когда блок от-указателя завершился):

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

Ограниченные указатели могут быть свободно присвоены неограниченным указателям, возможности оптимизации сохраняются, пока компилятор способен проанализировать код:

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++
}

Если тип массива объявлен с квалификатором типа restrict (через использование typedef), тип массива не квалифицируется как restrict, но его элементный тип квалифицируется:

(до C23)

Тип массива и его элементный тип всегда считаются одинаково квалифицированными как restrict:

(с 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

В объявлении функции ключевое слово restrict может появляться внутри квадратных скобок, используемых для объявления типа массива параметра функции. Оно квалифицирует тип указателя, в который преобразуется тип массива:

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)
}

Примечания

Предполагаемое использование квалификатора restrict (как и класс хранения register) состоит в том, чтобы способствовать оптимизации, и удаление всех экземпляров квалификатора из всех единиц предварительного перевода, составляющих соответствующую программу, не изменяет её смысл (т.е. наблюдаемое поведение).

Компилятор может игнорировать все или часть следствий алиасирования при использовании restrict.

Чтобы избежать неопределенного поведения, программист должен убедиться, что утверждения алиасирования, сделанные ограниченными указателями, не нарушаются.

Многие компиляторы предоставляют, как расширение языка, обратное restrict: атрибут, указывающий, что указатели могут быть алиасами, даже если их типы отличаются: may_alias (gcc),

Типовые способы использования

Существует несколько типовых способов использования ограниченных указателей:

Область файла

Указатель с квалификатором restrict в области файла должен указывать на один массив объектов в течение всего времени жизни программы. Этот объект массива не может быть ссылаться как через ограниченный указатель, так и через его объявленное имя (если оно есть) или другой ограниченный указатель.

Ограниченные указатели в области файла полезны для предоставления доступа к динамически выделенным глобальным массивам; семантика restrict позволяет оптимизировать обращения через этот указатель так же эффективно, как обращения к статическому массиву через его объявленное имя:

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

Параметр функции

Наиболее популярное применение ограниченных указателей — это использование в качестве параметров функций.

В приведенном ниже примере компилятор может сделать вывод, что нет алиасирования изменённых объектов, и поэтому агрессивно оптимизировать цикл. При входе в f, ограниченный указатель a должен предоставлять эксклюзивный доступ к связанному массиву. В частности, в f ни b ни c не могут указывать на массив, связанный с a, потому что ни одному из них не присваивается значение указателя на основании a. Для b, это очевидно из квалификатора const в его объявлении, но для c, требуется проверка тела 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); // 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
}

Обратите внимание, что c может указывать на массив, связанный с b. Обратите также внимание, что в этих целях «массив», связанный с конкретным указателем, означает только ту часть объекта массива, которая фактически ссылается через этот указатель.

Обратите внимание, что в приведённом выше примере компилятор может сделать вывод, что a и b не являются алиасами, потому что const-характер b гарантирует, что он не может зависеть от a в теле функции. Аналогично, программист мог бы написать void f(int n, float * a, float const * restrict b), в таком случае компилятор может сделать вывод, что объекты, на которые ссылается b, не могут быть изменены, а значит, ни один изменённый объект не может быть ссылаться с использованием как b, так и a. Если программист написал бы void f(int n, float * restrict a, float * b), компилятор не смог бы сделать вывод о неалиасировании a и b без проверки тела функции.

В общем случае лучше явно аннотировать все указатели, не допускающие алиасирования, в прототипе функции с restrict.

Область блока

Указатель с квалификатором restrict в области блока делает утверждение об алиасировании, ограниченное его областью. Он позволяет локальные утверждения, применимые только к важным блокам, таким как узкие циклы. Он также позволяет преобразовать функцию, принимающую указатели с квалификатором restrict, в макрос:

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)

Члены структуры

Область утверждения алиасирования, сделанного ограниченным указателем, который является членом структуры, — это область идентификатора, используемого для доступа к структуре.

Даже если структура объявлена в области файла, когда идентификатор, используемый для доступа к структуре, имеет область блока, утверждения алиасирования в структуре также имеют область блока; утверждения алиасирования действуют только в рамках выполнения блока или вызова функции, в зависимости от того, как был создан объект этого типа структуры:

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.
   // ...
}

Ключевые слова

restrict

Пример

Пример генерации кода; компилировать с -S (gcc, clang и т. д.) или /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;
}

Возможный вывод:

; 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

Ссылки

  • Стандарт C23 (ISO/IEC 9899:2023):
    • 6.7.3.1 Формальное определение restrict (с. TBD)
  • Стандарт C17 (ISO/IEC 9899:2018):
    • 6.7.3.1 Формальное определение restrict (с. 89-90)
  • Стандарт C11 (ISO/IEC 9899:2011):
    • 6.7.3.1 Формальное определение restrict (с. 123-125)
  • Стандарт C99 (ISO/IEC 9899:1999):
    • 6.7.3.1 Формальное определение restrict (с. 110-112)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/c/language/restrict

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API