Гарантирует ли `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,

  1. Будет ли компилятору разрешено выполнять эту оптимизацию (замените последнюю строку на return 42;) за foo_r? Если нет, есть ли (переносимый, если возможно) способ сообщить компилятору doesnt_modify не изменяет то, на что указывает его аргумент? Есть ли способ, которым компиляторы понимают и используют?

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

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