Является ли псевдоним указателя С++ угрозой, если указатели точно такие же?

Рассмотрим эту функцию, предназначенную для векторизации:

void AddSqr(float* restrict dst, float* restrict src, int cnt)
{
    for (int i=0; i<cnt; i++) dst[i] = src[i] * src[i];
};

Это будет работать, если src и dst, конечно, не имеют псевдонима. Но что, если src == dst? Крайние случаи, такие как src == dst+1, конечно, не допускаются. Но если указатели одинаковые, проблем быть не должно, или я что-то упускаю?

Изменить: ограничение — это ключевое слово компилятора Intel C++, MSVC имеет __restrict.

Моя точка зрения на этот вопрос заключается в том, что я не вижу способа, как любая векторизация может пойти не так: поскольку каждое значение dst зависит от одного значения src либо по совершенно другому (без псевдонимов), либо ТОЧНО по одному и тому же адресу, когда dst изменен, значение src больше никогда не понадобится, потому что тот факт, что оно было записано, означает, что вывод был рассчитан. Единственным случаем было бы, если бы компилятор использовал сам dst в качестве временного буфера, что я даже не думаю, что это правильно.


person mrzacek mrzacek    schedule 01.04.2015    source источник
comment
Разве это не вопрос C? C++ не имеет restrict.   -  person Angew is no longer proud of SO    schedule 01.04.2015
comment
С не знаю, но по моему стандарту выше указанное это УБ. Компилятор может выдать патологический код, предполагающий, что a[7] никогда не изменяется, даже после записи в b[7]. Есть ли причина выдавать этот код? Нет, но все еще действительный вывод.   -  person Yakk - Adam Nevraumont    schedule 01.04.2015
comment
Я позволил себе отредактировать теги на C, поскольку restrict не является ключевым словом C++. OP, если вы не согласны, отметьте его обратно, но, пожалуйста, также отредактируйте объяснение того, как вы используете функциональность C в C ++.   -  person Angew is no longer proud of SO    schedule 01.04.2015
comment
Ограничение на самом деле является специфичным для Intel C++ ключевым словом, вместо этого у MSVC есть _restrict (но я думаю, что оно также принимает ограничения).   -  person mrzacek mrzacek    schedule 01.04.2015
comment
restrict — это квалификатор типа в C11. Черновик n1570   -  person Leonard Michlmayr    schedule 01.04.2015


Ответы (3)


В C ваш код вызывает поведение undefined, нарушая определение restrict, потому что он записывает в один объект через dst, но читает тот же объект через src.

Не имеет значения, есть ли смещение между dst и src; условие состоит в том, что существует объект float, который записывается через один указатель и читается через другой.

person M.M    schedule 01.04.2015

Restrict — это ключевое слово, разрешающее некоторые оптимизации, которые действительны только в том случае, если два указателя не мешают друг другу.

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

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

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

person user3710044    schedule 01.04.2015

Спасибо за все ответы, ребята. Итак: - По стандартному определению С++ это действительно неверно. - Однако я получил ответ непосредственно от Intel, что это нормально.

Мой первоначальный вопрос действительно был не о том, «соответствует ли он правилам», а о том, есть ли вероятность того, что это может пойти не так. Массивы src/dst отображаются 1:1, так что либо массивы совершенно разные, либо абсолютно одинаковые, следовательно, каждый элемент зависит либо от какого-то совершенно не связанного элемента, либо от самого себя. Поэтому, если элемент переписывается, его окончательное значение сохраняется и больше никогда не понадобится в течение цикла.

В любом случае я сделал дополнительную обработку:

void AddSqr(float* restrict dst, float* restrict src, int cnt)
{
    if (dst == src)
        for (int i=0; i<cnt; i++) dst[i] = dst[i] * dst[i];
    else
        for (int i=0; i<cnt; i++) dst[i] = src[i] * src[i];
};

Это должно решить потенциальную проблему и даже предоставить некоторые дополнительные возможности оптимизации, так как в случае, если указатели одинаковы, компилятор может использовать только один регистр (или не использовать регистр смещения) для нацеливания на массивы.

person mrzacek mrzacek    schedule 02.04.2015