Подтвердить что ты не робот

Ограничивает ли помощь в C, если указатель уже отмечен как const?

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

// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
     for (size_t i = 0; i < size; ++i)
     {
         result [i] = a [0] * b [i];
     }
}

Если компилятор должен предположить, что result может перекрываться с a, он должен каждый раз обновлять его. Но, поскольку a отмечен const, компилятор также может предположить, что a является фиксированным, и, следовательно, выборка один раз в порядке.

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

4b9b3361

Ответ 1

Ваш указатель const, говорящий кому-либо, вызывающему вашу функцию, чтобы вы не касались данных, на которые указана эта переменная. К сожалению, компилятор до сих пор не знает, является ли результат псевдонимом указателей const. Вы всегда можете использовать указатель не const, как const-указатель. Например, многие функции принимают указатель const char (т.е. string) в качестве параметра, но вы можете, если хотите, передать ему неконстантный указатель, функция просто дает вам обещание, что оно не будет использовать этот конкретный указатель на изменение чего-либо.

В принципе, чтобы приблизиться к вашему вопросу, вам нужно добавить ограничение на a и b, чтобы "обещать" компилятору, что тот, кто использует эту функцию, не будет передаваться в результате как псевдоним a или b. Предполагая, что вы, конечно, можете сделать такое обещание.

Ответ 2

В стандарте C-99 (ISO/IEC 9899: 1999 (E)) имеются примеры const * restrict, например, в разделе 7.8.2.3:

Функции strtoimax и strtoumax

Сводка

#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
                   char ** restrict endptr, int base);
--- snip ---

Следовательно, если предположить, что стандарт не предоставит такой пример, если const * были избыточными для * restrict, то они действительно не являются избыточными.

Ответ 3

Все здесь кажутся очень смущенными. Пока нет ни одного примера указателя const в любом ответе.

Объявление const float* a - это не указатель const, это константное хранилище. Указатель по-прежнему изменен. float *const a - это указатель на const с изменяемым поплавком.

Итак, вопрос должен быть, есть ли какая-либо точка в float *const restrict a (или const float *const restrict a, если вы предпочитаете).

Ответ 4

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

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

В отличие от restrict, использование указателя-to-const для изменяемых данных в основном является обещанием для других людей, а не для компилятора. Отключение const по всему месту не приведет к неправильному поведению оптимизатора (AFAIK), если вы не попытаетесь изменить что-то, что компилятор помещает в постоянную память (см. Ниже о переменных static const). Если компилятор не может увидеть определение функции при оптимизации, он должен предположить, что он отбрасывает const и изменяет данные через этот указатель (т.е. Что функция не учитывает const ее указателей args).

Компилятор знает, что static const int foo = 15; не может изменить, но и надежно встроить значение, даже если вы передадите свой адрес неизвестным функциям. (Вот почему static const int foo = 15; не медленнее, чем #define foo 15 для оптимизирующего компилятора. Хорошие компиляторы оптимизируют его как constexpr, когда это возможно.)


Помните, что restrict является обещанием компилятору, что все, что вы получаете через этот указатель, не перекрывается ни с чем другим. Если это не так, ваша функция не обязательно будет делать то, что вы ожидаете. например не вызывайте foo_restrict(buf, buf, buf) для работы на месте.

В моем опыте (с gcc и clang) restrict в основном полезен для указателей, которые вы сохраняете. Это не помешает помещать restrict в указатели источника, но обычно вы получаете все возможное улучшение asm от размещения его только указателя (ов) назначения, если все магазины, которые выполняет ваша функция, проходят через restrict указатели.

Если у вас есть какие-либо вызовы функций в вашем цикле, restrict на указателе источника позволяет clang (но не gcc) избегать перезагрузки. См. эти тестовые примеры в проводнике компилятора Godbolt, в частности:

void value_only(int);  // a function the compiler can't inline

int arg_pointer_valonly(const int *__restrict__ src)
{
    // the compiler needs to load `*src` to pass it as a function arg
    value_only(*src);
    // and then needs it again here to calculate the return value
    return 5 + *src;  // clang: no reload because of __restrict__
}

gcc6.3 (с таргетингом на x86-64 SysV ABI) решает сохранить src (указатель) в регистре, сохраняемом при вызове вызова функции, и перезагрузить *src после вызова. Либо gcc-алгоритмы не рассматривали эту возможность оптимизации, либо решили, что это не стоит, или специально разработанные разработчики gcc не реализовали ее, потому что считают ее небезопасной. IDK, который. Но поскольку clang делает это, я предполагаю, что это, вероятно, законно в соответствии со стандартом C11.

clang4.0 оптимизирует это, чтобы загружать только *src один раз и сохранять значение в регистре, сохраняемом вызовом, через вызов функции. Без restrict он этого не делает, потому что вызываемая функция может (как побочный эффект) изменить *src на другой указатель.

Вызывающий этой функции мог передать адрес глобальной переменной, например. Но любая модификация *src, кроме как с помощью указателя src, нарушит обещание, которое restrict сделало компилятору. Поскольку мы не передаем src в valonly(), компилятор может предположить, что он не изменяет значение.

Диалог GNU C позволяет использовать __attribute__((pure)) или __attribute__((const)), чтобы заявить, что функция не имеет побочных эффектов, позволяя эту оптимизацию без restrict, но нет портативного эквивалента в ISO C11 (AFAIK). Разумеется, включение функции inline (путем помещения ее в файл заголовка или использование LTO) также допускает такую ​​оптимизацию и намного лучше для небольших функций, особенно если вызывается внутри циклов.


Компиляторы, как правило, довольно агрессивно относятся к оптимизации, которую допускает стандарт, даже если они удивляют некоторых программистов и нарушают некоторые из существующих небезопасных кодов, которые сработали. (C настолько портативен, что многие вещи - это поведение undefined в базовом стандарте, большинство замечательных реализаций определяют поведение множества вещей, которые стандарт оставляет как UB.) C не является языком, на котором безопасно бросать код на компилятор до тех пор, пока он не сделает то, что вы хотите, не проверив, что вы делаете это правильно (без переполнения целых чисел и т.д.).


Если вы посмотрите на выход asm x86-64 asm для компиляции вашей функции (из вопроса), вы можете легко увидеть разницу. Я положил его на проводник компилятора Godbolt.

В этом случае положить restrict на a достаточно, чтобы позволить clang поднимать нагрузку a[0], но не gcc.

С помощью float *restrict result и clang, и gcc будут поднимать нагрузку.

например.

# gcc6.3, for foo with no restrict, or with just const float *restrict a
.L5:
    vmovss  xmm0, DWORD PTR [rsi]
    vmulss  xmm0, xmm0, DWORD PTR [rdx+rax*4]
    vmovss  DWORD PTR [rdi+rax*4], xmm0
    add     rax, 1
    cmp     rcx, rax
    jne     .L5

против.

# gcc 6.3 with   float *__restrict__ result
# clang is similar with const float *__restrict__ a but not on result.
    vmovss  xmm1, DWORD PTR [rsi]   # outside the loop
.L11:
    vmulss  xmm0, xmm1, DWORD PTR [rdx+rax*4]
    vmovss  DWORD PTR [rdi+rax*4], xmm0
    add     rax, 1
    cmp     rcx, rax
    jne     .L11

Итак, поместите __restrict__ на все указатели, которые гарантированно не перекрываются с чем-то другим.


BTW, restrict - это только ключевое слово в C. Некоторые компиляторы С++ поддерживают __restrict__ или __restrict как расширение, поэтому вы должны #ifdef прочь на неизвестных компиляторах.

Так как

Ответ 5

Как указывалось в предыдущем ответе, вам нужно добавить "ограничиться". Я также хотел прокомментировать ваш сценарий, что "результат может совпадать с". Это не единственная причина, по которой компилятор обнаружит, что "а" может измениться. Он также может быть изменен другим потоком с указателем на "a". Таким образом, даже если ваша функция не изменила никаких значений, компилятор все равно предположит, что "a" может измениться.