Ограничить - Википедия - restrict

в Язык программирования C, ограничивать это ключевое слово что можно использовать в указатель декларации. Добавляя этот квалификатор типа, программист указывает на компилятор что на время жизни указателя только сам указатель или значение, непосредственно полученное из него (например, указатель + 1) будет использоваться для доступа к объекту, на который он указывает.

ограничивать ограничивает эффекты сглаживание указателя, помощь оптимизации. Если декларация о намерениях не соблюдается и к объекту обращается независимый указатель, это приведет к неопределенное поведение. Использование этого квалификатора типа позволяет коду C достичь той же производительности, что и та же программа, написанная на Фортран. Он был введен в Стандарт C99.[1]

C ++ не имеет стандартной поддержки для ограничивать, но у многих компиляторов есть эквиваленты, которые обычно работают как на C ++, так и на C, например GCC 'песок Лязг с __ограничивать__, и Visual C ++ с __declspec (ограничить). Кроме того, __ограничивать поддерживается этими тремя компиляторами. Точная интерпретация этих альтернативных ключевых слов зависит от компилятора:

  • В компиляторах в стиле Unix, таких как GCC и Clang, __ограничивать и __ограничивать__ означают то же самое, что и их коллега C. Расширения включают возможность применения их к ссылочным типам и это.[2]
  • В Visual C ++ предоставляется несколько квалификаторов без псевдонима:
    1. __declspec (ограничить) применяется к объявлению функции и намекает, что вернулся указатель не имеет псевдонима.
    2. __ограничивать используется там же, где и ограничивать, но подсказка без псевдонима не распространяется, как в ограничивать. Он также продлен на типы профсоюзов.

Оптимизация

Если компилятор знает, что существует только один указатель на блок памяти, он может создать более оптимизированный код. Например:

пустота updatePtrs(size_t *ptrA, size_t *ptrB, size_t *вал){  *ptrA += *вал;  *ptrB += *вал;}

В приведенном выше коде указатели ptrA, ptrB, и вал мог бы обратитесь к то же место в памяти, поэтому компилятор может сгенерировать менее оптимальный код:

; Гипотетическая RISC-машина.ldr r12, [вал]     ; Загрузить память с val до r12.ldr r3, [ptrA]     ; Загрузить память с ptrA в r3.Добавить r3, r3, r12    ; Произвести сложение: r3 = r3 + r12.ул r3, [ptrA]     ; Сохраните r3 в ячейке памяти ptrA, обновив значение.ldr r3, [ptrB]     ; 'load', возможно, придется дождаться завершения предыдущего 'store'ldr r12, [вал]     ; Придется загружать второй раз, чтобы обеспечить единообразиеДобавить r3, r3, r12ул r3, [ptrB]

Однако если ограничивать используется ключевое слово, а указанная выше функция объявляется как

пустота updatePtrs(size_t *ограничивать ptrA, size_t *ограничивать ptrB, size_t *ограничивать вал);

тогда компилятору разрешено предполагать который ptrA, ptrB, и вал указывают на разные местоположения, и обновление ячейки памяти, на которую ссылается один указатель, не повлияет на ячейки памяти, на которые ссылаются другие указатели. Программист, а не компилятор, отвечает за то, чтобы указатели не указывали на идентичные места. Компилятор может, например, переставьте код, сначала загрузив все ячейки памяти, а затем выполняя операции перед фиксацией результатов обратно в память.

ldr r12, [вал]  ; Обратите внимание, что теперь val загружается только один разldr r3, [ptrA]  ; Кроме того, вся нагрузка в начале ..-.ldr r4, [ptrB]Добавить r3, r3, r12Добавить r4, r4, r12ул r3, [ptrA]  ; ... все магазины в конце концов.ул r4, [ptrB]

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

Обратите внимание, что реальный сгенерированный код может иметь другое поведение. Преимущество приведенного выше мини-примера обычно невелико, и в реальных случаях большие циклы, выполняющие тяжелый доступ к памяти, как правило, являются тем, что действительно помогает ограничить.

Как упоминалось выше, поведение неправильного кода неопределенный, компилятор гарантирует, что сгенерированный код работает правильно, только если код следует декларации о намерениях.

Предупреждения компилятора

Чтобы предотвратить неправильный код, некоторые компиляторы и другие инструменты пытаются определить, когда перекрывающиеся аргументы были переданы функциям с параметрами, отмеченными ограничивать.[3] В Стандарт кодирования CERT C считает неправомерное использование ограничивать и библиотечные функции, отмеченные им (EXP43-C), являются вероятным источником ошибок программного обеспечения, хотя по состоянию на ноябрь 2019 года не известно, что это было вызвано уязвимостями.[4]

Рекомендации

  1. ^ Ульрих Дреппер (23 октября 2007 г.). «Память, часть 5: что умеют программисты». Что каждый программист должен знать о памяти. lwn.net. ... Стандартные правила псевдонимов языков C и C ++ не помогают компилятору принимать эти решения (если не используется ограничение, все обращения к указателям являются потенциальными источниками псевдонимов). Вот почему Fortran по-прежнему является предпочтительным языком для числового программирования: он упрощает написание быстрого кода. (Теоретически ключевое слово restrict, введенное в язык C в версии 1999 г., должно решить эту проблему. Однако компиляторы еще не догнали его. Причина в основном в том, что существует слишком много некорректного кода, который может ввести компилятор в заблуждение и привести к его генерации некорректных объектный код.)
  2. ^ «Ограниченные указатели». Использование коллекции компиляторов GNU (GCC).
  3. ^ «Параметры предупреждений: -Wrestrict». GCC. Получено 19 ноября 2019.
  4. ^ «EXP43-C. Избегайте неопределенного поведения при использовании ограниченных указателей». Стандарт кодирования SEI CERT C. Получено 19 ноября 2019.

внешняя ссылка