restrict type qualifier (since C99)
Каждый отдельный тип в
системе типов
C имеет несколько
квалифицированных
версий этого типа, соответствующих одному, двум или всем трём квалификаторам:
const
,
volatile
и, для указателей на объектные типы,
restrict
. На этой странице описываются эффекты квалификатора
restrict
.
Только указатель на object type или (возможно, многомерный) массив таковых (since 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++; // ни один из объектов, изменяемых через *p, не совпадает // ни с одним из объектов, читаемых через *q // компилятор может оптимизировать, векторизовать, отображать страницы и т.д. } void g(void) { extern int d[100]; f(50, d + 50, d); // OK f(50, d + 1, d); // Неопределенное поведение: d[1] доступен через оба указателя p и q в f }
Если объект никогда не изменяется, он может быть алиасирован и доступен через различные указатели с квалификатором restrict (обратите внимание, что если объекты, на которые указывают алиасированные указатели с квалификатором restrict, в свою очередь являются указателями, этот алиасинг может препятствовать оптимизации).
Присваивание одного ограниченного указателя другому является неопределенным поведением, за исключением случаев, когда присваивание происходит от указателя на объект в некотором внешнем блоке к указателю в некотором внутреннем блоке (включая использование аргумента-ограниченного-указателя при вызове функции с параметром-ограниченным-указателем) или при возврате из функции (и в других случаях, когда блок from-указателя завершился):
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // неопределённое поведение
Ограниченные указатели могут свободно присваиваться неограниченным указателям, возможности оптимизации сохраняются до тех пор, пока компилятор способен анализировать код:
void f(int n, float * restrict r, float * restrict s) { float *p = r, *q = s; // OK while (n-- > 0) *p++ = *q++; // почти наверняка оптимизируется так же, как *r++ = *s++ }
|
Если тип массива объявлен с квалификатором типа restrict (посредством использования typedef ), то тип массива не является квалифицированным как restrict, но его тип элементов является таковым: |
(до C23) |
|
Тип массива и его тип элементов всегда считаются идентично квалифицированными как restrict: |
(начиная с C23) |
typedef int *array_t[10]; restrict array_t a; // тип a - int *restrict[10] // Примечание: clang и icc отвергают это на том основании, что array_t не является типом указателя void *unqual_ptr = &a; // OK до C23; ошибка начиная с C23 // Примечание: clang применяет правило из C++/C23 даже в режимах C89-C17
В объявлении функции ключевое слово
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); // возможно неопределенное поведение (зависит от того, что делает f) }
Содержание |
Примечания
Предназначение квалификатора restrict (как и класса памяти register) заключается в оптимизации, и удаление всех экземпляров этого квалификатора из всех единиц трансляции препроцессора, составляющих корректную программу, не изменяет её семантику (т.е. наблюдаемое поведение).
Компилятор имеет право игнорировать любые или все следствия псевдонимов при использовании
restrict
.
Чтобы избежать неопределенного поведения, программист должен гарантировать, что утверждения о псевдонимах, сделанные указателями с квалификатором restrict, не нарушаются.
Многие компиляторы предоставляют, в качестве расширения языка, противоположность
restrict
: атрибут, указывающий, что указатели могут быть алиасами, даже если их типы различаются:
may_alias
(gcc),
Паттерны использования
Существует несколько распространенных шаблонов использования указателей с квалификатором restrict:
Область видимости файла
Указатель с квалификатором restrict на уровне файла должен указывать на один массив в течение всего времени выполнения программы. Этот массив не может быть доступен одновременно через restrict-указатель и через его объявленное имя (если оно есть) или другой restrict-указатель.
Указатели с ограниченной областью видимости файла полезны для предоставления доступа к динамически выделенным глобальным массивам; семантика restrict позволяет оптимизировать обращения через этот указатель так же эффективно, как обращения к статическому массиву через его объявленное имя:
float *restrict a, *restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; // a указывает на первую половину b = t + n; // b указывает на вторую половину } // компилятор может вывести из квалификаторов restrict, что // нет потенциального псевдонимирования между именами a, b и c
Параметр функции
Наиболее популярный случай использования указателей с квалификатором restrict - это применение в качестве параметров функций.
В следующем примере компилятор может сделать вывод об отсутствии псевдонимов для изменяемых объектов и агрессивно оптимизировать цикл.
При входе в
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); // неопределённое поведение c = d; f( 99, d+1, e); // неопределённое поведение f( 99, e, d+1); // OK }
Обратите внимание, что допускается, чтобы c указывал в массив, связанный с b. Также обратите внимание, что для этих целей "массив", связанный с конкретным указателем, означает только ту часть объекта массива, которая фактически адресуется через этот указатель.
Обратите внимание, что в приведенном выше примере компилятор может сделать вывод, что a и b не являются алиасами, поскольку константность 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)
Члены структуры
Область действия утверждения о псевдонимах, сделанного указателем с квалификатором restrict, который является членом структуры, является областью видимости идентификатора, используемого для доступа к структуре.
Даже если структура объявлена на уровне файла, когда идентификатор, используемый для доступа к структуре, имеет блочную область видимости, утверждения псевдонимов в структуре также имеют блочную область видимости; утверждения псевдонимов действуют только внутри выполнения блока или вызова функции, в зависимости от того, как был создан объект этого типа структуры:
struct t // Ограниченные указатели гарантируют, что { int n; // члены структуры указывают на непересекающиеся области памяти. float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r,s,u имеют область видимости блока // r.p, r.q, s.p, s.q, u.p, u.q должны указывать на // непересекающиеся области памяти во время каждого выполнения ff. // ... }
Ключевые слова
Пример
пример генерации кода; компилировать с -S (gcc, clang, etc) или /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; }
` был сохранен без изменений, как и требовалось. HTML-разметка и атрибуты также остались нетронутыми.
Возможный вывод:
; сгенерированный код для 64-битной платформы Intel: foo: movl $5, (%rdi) ; сохранить 5 в *a movl $6, (%rsi) ; сохранить 6 в *b movl (%rdi), %eax ; повторное чтение из *a на случай изменения предыдущей операцией записи addl $6, %eax ; прибавить 6 к значению, прочитанному из *a ret rfoo: movl $11, %eax ; результат равен 11, вычисляется на этапе компиляции movl $5, (%rdi) ; сохранить 5 в *a movl $6, (%rsi) ; сохранить 6 в *b ret
Ссылки
- Стандарт C23 (ISO/IEC 9899:2024):
-
- 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)