Понимание ограничить классификатор на примерах

restrict поведение ключевого слова определено в C99 6.7.3.1:

Пусть D будет объявлением обычного идентификатора, который предоставляет средство для обозначения объекта P в качестве ограниченно-квалифицированного указателя на тип T.

Если D появляется внутри блока и не имеет внешнего класса хранения, пусть B обозначает блок. Если D появляется в списке объявлений параметров определения функции, пусть B обозначает связанный блок. В противном случае пусть B обозначает блок main (или блок любой функции, вызываемой при запуске программы в автономной среде).

В последующем выражение указателя E называется основанным на объекте P, если (в некоторой точке последовательности при выполнении B до оценки E) модифицировать P так, чтобы он указывал на копию объекта массива, на который он ранее указывал изменит значение E.119) Обратите внимание, что ''based'' определено только для выражений с типами указателей.

Во время каждого выполнения B пусть L будет любым lvalue, имеющим &L на основе P. Если L используется для доступа к значению объекта X, который он обозначает, и X также изменяется (любым способом), то применяются следующие требования: T не должен быть постоянным. Каждое другое lvalue, используемое для доступа к значению X, также должно иметь свой адрес на основе P. Любой доступ, который изменяет X, должен также рассматриваться как модифицирующий P, для целей этого подпункта. Если P присваивается значение выражения E указателя, которое основано на другом объекте ограниченного указателя P2, связанном с блоком B2, то либо выполнение B2 должно начинаться до выполнения B, либо выполнение B2 должно заканчиваться до назначение. Если эти требования не выполняются, то поведение не определено.

Как и всем остальным, мне трудно понять все тонкости этого определения. В качестве ответа на этот вопрос я хотел бы увидеть набор хороших примеров для каждого требования в 4-м абзаце случаев использования, которые будут нарушать это требование. Эта статья:

http://web.archive.org/web/20120225055041/http://developers.sun.com/solaris/articles/cc_restrict.html

хорошо представляет правила в терминах "компилятор может предположить..."; Расширение этого паттерна и связывание предположений, которые может сделать компилятор, и того, как они не выдерживают, с каждым примером было бы замечательно.

1 ответ

Ниже я буду ссылаться на примеры использования из статьи 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, код для 1-й функции будет прямым:

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, во 2-й функции может быть наложение. В этом случае нам нужно проверить, расположен ли исходный массив перед местом назначения или наоборот, и соответственно выбрать границы индекса цикла.

Например, скажем 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 Модификатор дает шанс для лучшей оптимизации скорости, исключая лишний код и принятие решения в противном случае.

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


1-й вариант использования (init() функция) можно рассматривать как вариант 2-го, описанного выше. Здесь два массива создаются с помощью одного динамического вызова выделения памяти.

Обозначая два указателя как restrict-ed включает оптимизацию, при которой порядок команд будет иметь значение в противном случае. Например, если у нас есть код:

a1[5] = 4;
a2[3] = 8;

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

OTOH, если указатели не restrictТаким образом, важно, чтобы 1-е задание было выполнено до второго. Это потому, что есть вероятность того, что a1[5] а также a2[3] на самом деле одно и то же место памяти! Легко видеть, что в этом случае конечное значение должно быть 8. Если мы изменим порядок команд, конечное значение будет равно 4!

Опять же, если на это даны непересекающиеся указатели restrictПредполагаемый код, результат не определен.

Другие вопросы по тегам