Intereting Posts
Самый быстрый способ скопировать несколько двумерных массивов в один одномерный массив (в C) Как сделать функцию для чтения массива в c? Поведение scanf () инициализация переменной структуры C4996 (функция небезопасная) предупреждение для strcpy, но не для memcpy я не могу понять, как читать строки (слова) из входного файла в связанный список Неплохая ли практика объявлять среднюю функцию массива? Получение адреса пользовательского типа данных в C из Python с использованием ctypes Обнаружено повреждение кучи: после нормального блока Должен ли я освобождать память перед выходом? Зачем использовать xor с литералом вместо инверсии (побитовое) Есть ли способ вызвать библиотеку thread-local init / cleanup при создании / уничтожении streamов? Есть ли функция для вызова дампа стека в C? Текущий статус выполнения C11 ()? У моего числа с плавающей запятой есть дополнительные цифры при печати

Понимание ограничителя по примерам

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

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

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

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

Во время каждого исполнения B пусть L – любое l-значение, которое имеет & L на основе P. Если L используется для доступа к значению объекта X, который он обозначает, а X также изменяется (любыми способами), тогда применяются следующие требования : T не должен быть const-квалифицированным. Каждая другая 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

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

Ниже я буду ссылаться на статьи из бумаги Солнца, связанные с этим вопросом.

(Относительно) очевидным случаем был бы случай mem_copy (), который подпадает под категорию 2-й категории пользователей в документе Sun f1() функция 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 

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=0; i--) s1[i] = s2[i]; return; } 

Легко понять, как restrict модификатор дает возможность улучшить оптимизацию скорости, исключая дополнительный код и решение if-else.

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


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

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

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

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

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

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