Гарантирует ли `const T *restrict`, что указанный объект не изменен?
Рассмотрим следующий код:
void doesnt_modify(const int *);
int foo(int *n) {
*n = 42;
doesnt_modify(n);
return *n;
}
где определение doesnt_modify
не виден для компилятора. Таким образом, следует предположить, что doesnt_modify
меняет объект n
указывает и должен прочитать *n
перед return
(последняя строка не может быть заменена return 42;
).
Предполагать, doesnt_modify
не модифицирует *n
, Я подумал о следующем, чтобы разрешить оптимизацию:
int foo_r(int *n) {
*n = 42;
{ /* New scope is important, I think. */
const int *restrict n_restr = n;
doesnt_modify(n_restr);
return *n_restr;
}
}
Это имеет тот недостаток, что вызывающий doesnt_modify
должен сказать компилятору *n
не изменен, вместо этого сама функция может сообщить компилятору через свой прототип. Просто restrict
-квалифицирующий параметр doesnt_modify
в декларации не хватает, ср. "Это на высшем уровне volatile
или же restrict
значительный [...]? ".
При компиляции с gcc -std=c99 -O3 -S
(или Clang с одинаковыми параметрами), все функции компилируются в эквивалентную сборку, все перечитывают 42
от *n
,
Будет ли компилятору разрешено выполнять эту оптимизацию (замените последнюю строку на
return 42;
) заfoo_r
? Если нет, есть ли (переносимый, если возможно) способ сообщить компиляторуdoesnt_modify
не изменяет то, на что указывает его аргумент? Есть ли способ, которым компиляторы понимают и используют?Есть ли какая-либо функция UB (при условии
doesnt_modify
не изменяет аргумент его аргумента)?
Почему я думаю, restrict
может помочь здесь (из C11 (n1570) 6.7.3.1 "Формальное определение restrict
”, P4 [emph. мой]):
[В этом случае, B
это внутренний блок foo_r
, P
является n_restr
, T
является const int
, а также X
обозначен ли объект как *n
, Я думаю.]
Во время каждого исполнения
B
, позволятьL
быть любым значением, которое имеет&L
основанный наP
, ЕслиL
используется для доступа к значению объектаX
что это обозначает, иX
также изменяется (любым способом), тогда применяются следующие требования:T
не должен быть постоянным. [...]
$ clang --version
Ubuntu clang version 3.5.0-4ubuntu2 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
Target: x86_64-pc-linux-gnu
Версия Gcc 4.9.2 для 32-разрядной цели x86.
4 ответа
Версия 1, кажется, четко определена формальным определением restrict
(С11 6.7.3.1). Для следующего кода:
const int *restrict P = n;
doesnt_modify(P);
return *P;
символы, используемые в 6.7.3.1:
- B - этот блок кода
- P - переменная
P
- Т - тип
*P
которыйconst int
- X - (неконстантный) int, на который указывает
P
- L - значение
*P
это то, что нас интересует
6.7.3.1/4 (частично):
Во время каждого исполнения
B
, позволятьL
быть любым значением, которое имеет&L
основанный наP
, ЕслиL
используется для доступа к значению объектаX
что это обозначает, иX
также изменяется (любым способом), тогда применяются следующие требования:T
не должны быть константно-квалифицированными [...]. Если эти требования не выполняются, то поведение не определено.
Обратите внимание, что T
является квалифицированным. Следовательно, если X
изменяется любым образом во время этого блока (который включает в себя во время вызова функции в этом блоке), поведение не определено.
Поэтому компилятор может оптимизировать как doesnt_modify
не модифицировал X
,
Версия 2 немного сложнее для компилятора. 6.7.6.3/15 говорится, что квалификаторы верхнего уровня не рассматриваются в совместимости с прототипами - хотя они не полностью игнорируются.
Итак, хотя прототип говорит:
void doesnt_modify2(const int *restrict p);
еще может быть, что тело функции объявлено как void doesnt_modify2(const int *p)
и, следовательно, может изменить *p
,
Мой вывод заключается в том, что если и только если компилятор может увидеть определение для doesnt_modify2
и подтвердите, что p
объявлен restrict
в списке параметров определения тогда можно будет выполнить оптимизацию.
В общем-то, restrict
означает, что указатель не является псевдонимом (т. е. только он или указатель, полученный из него, может использоваться для доступа к указанному объекту).
С const
это означает, что указанный объект не может быть изменен с помощью правильно сформированного кода.
Однако ничто не мешает программисту нарушать правила, используя явное преобразование типов для удаления const
Несс. Затем компилятор (после того, как его избили программистом) разрешит попытку изменить указанный объект без каких-либо жалоб. Строго говоря, это приводит к неопределенному поведению, поэтому разрешается любой мыслимый результат, в том числе - возможно, - изменение указанного объекта.
Если нет, есть ли (переносимый, если возможно) способ сказать, что компилятор не изменяет то, на что указывает его аргумент?
Нет такого способа.
Компилятор оптимизаторы сталкиваются с трудностями при оптимизации параметров указателя и ссылки функции участвуют. Поскольку реализация этой функции может отбрасывать константность, компиляторы предполагают, что T const*
так же плохо, как T*
,
Следовательно, в вашем примере после звонка doesnt_modify(n)
это должно перезагрузить *n
из памяти.
См. 2013 Keynote: Chandler Carruth: Оптимизация новых структур C++. Это относится и к Си.
Добавление restrict
Ключевое слово здесь не меняет вышеперечисленное.
Одновременное использование restrict
квалификатор для параметра типа указателя и const
Спецификатор его целевого типа приглашает компилятор предполагать, что ни одна область памяти, к которой осуществляется доступ в течение времени жизни объекта-указателя через содержащийся в нем указатель или любой производный от него указатель, не будет изменена любыми средствами в течение времени существования этого указателя. Как правило, он ничего не говорит об областях памяти, к которым нет доступа с помощью указанного указателя.
Единственные ситуации, когда const restrict
будет иметь последствия для всего объекта будут те, где указатель объявлен с использованием синтаксиса массива с static
связаны. В этой ситуации поведение будет определяться только в тех случаях, когда весь объект массива может быть прочитан (без вызова UB). Поскольку чтение любой части объекта массива, которая изменяется во время выполнения функции, вызовет UB, коду будет позволено предположить, что никакая часть массива не может быть изменена каким-либо образом.
К сожалению, в то время как компилятор, который знал, что фактическое определение функции начинается с:
void foo(int const thing[restrict static 1]);
будет иметь право предполагать, что ни одна часть *thing
будет изменен во время выполнения функции, даже если объект может быть таким, к которому функция могла бы получить доступ через указатель, не производный от thing
тот факт, что прототип функции включает в себя такие квалификаторы, не заставит ее определение поступить аналогичным образом.