Ответ 1
Ниже я буду ссылаться на usecases из бумаги Sun, связанной с этим вопросом.
(относительно) очевидным случаем был бы случай mem_copy(), который подпадает под категорию 2-й категории пользователей в документе Sun (функция f1()
). Скажем, мы имеем следующие две реализации:
void mem_copy_1(void * restrict s1, const void * restrict s2, size_t n);
void mem_copy_2(void * s1, const void * s2, size_t n);
Поскольку мы знаем, что между двумя массивами, на которые указывают s1 и s2, нет совпадения, код для первой функции будет прямым:
void mem_copy_1(void * restrict s1, const void * restrict s2, size_t n)
{
// naively copy array s2 to array s1.
for (int i=0; i<n; i++)
s1[i] = s2[i];
return;
}
s2 = '....................1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde....................' <- s1 after the naive copy
s2 = '....................1234567890abcde' <- s2 after the naive copy
OTOH, во второй функции может быть перекрытие. В этом случае нам нужно проверить, находится ли исходный массив до адресата или наоборот, и соответственно выбрать границы индекса цикла.
Например, скажем s1 = 100
и s2 = 105
. Тогда, если n=15
, после копии вновь скопированный массив s1
переполнит первые 10 байтов исходного массива s2
. Мы должны убедиться, что сначала скопировали нижние байты.
s2 = '.....1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde.....' <- s1 after the naive copy
s2 = '.....67890abcdeabcde' <- s2 after the naive copy
Однако, если, s1 = 105
и s2 = 100
, то сначала записывание нижних байтов будет превышать последние 10 байтов источника s2
, и мы получим ошибочную копию.
s2 = '1234567890abcde.....' <- s2 before the naive copy
s1 = '.....123451234512345' <- s1 after the naive copy - not what we wanted
s2 = '123451234512345.....' <- s2 after the naive copy
В этом случае нам нужно сначала скопировать последние байты массива, возможно, отступить назад. Код будет выглядеть примерно так:
void mem_copy_2(void *s1, const void *s2, size_t n)
{
if (((unsigned) s1) < ((unsigned) s2))
for (int i=0; i<n; i++)
s1[i] = s2[i];
else
for (int i=(n-1); i>=0; i--)
s1[i] = s2[i];
return;
}
Легко видеть, как модификатор restrict
дает возможность улучшить оптимизацию скорости, исключая дополнительный код и решение if-else.
В то же время эта ситуация опасна для неосторожного программиста, который передает перекрывающиеся массивы функции restrict
-ed. В этом случае для обеспечения правильного копирования массива нет защитных ограждений. В зависимости от пути оптимизации, выбранного компилятором, результатом является undefined.
1-я утилиза (функция init()
) может рассматриваться как вариация на втором, описанная выше. Здесь два массива создаются с одним вызовом выделения динамической памяти.
Назначение двух указателей как restrict
-ed позволяет оптимизировать, в котором порядок инструкций будет иметь значение в противном случае. Например, если у нас есть код:
a1[5] = 4;
a2[3] = 8;
тогда оптимизатор может изменить порядок этих операторов, если он сочтет это полезным.
OTOH, если указатели не restrict
-ed, то важно, чтобы первое назначение выполнялось до второго. Это связано с тем, что существует возможность того, что a1[5]
и a2[3]
на самом деле являются тем же самым местом памяти! Легко видеть, что когда это так, тогда конечное значение должно быть 8. Если мы изменим порядок инструкций, то конечное значение будет 4!
Опять же, если непересекающиеся указатели заданы этому restrict
-принятому коду, результат undefined.