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

Являются ли современные компиляторы С++ способными дважды вызвать функцию const при некоторых условиях?

Например, если у меня есть этот код:

class SomeDataProcessor
{
public:
    bool calc(const SomeData & d1, const SomeData & d2) const;
private:
    //Some non-mutable, non-static member variables
}

SomeDataProcessor sdp;
SomeData data1;
SomeData data2;

someObscureFunction(sdp.calc(data1, data2),
                    sdp.calc(data1, data2));

Рассмотрим эквивалентный код потенциально:

bool b = sdp.calc(data1, data2);
someObscureFunction(b,b);

Чтобы это было правильно, функция calc() должна удовлетворять некоторым требованиям, и для примера я вызываю свойство _pure_const_formula_

A _pure_const_formula_:

  • Не изменять член, статическое или глобальное переменное состояние
  • Вызов только _pure_const_formula_ функции
  • Возможно, некоторые другие условия, о которых я не помню

Например, вызов генератора случайных чисел не соответствовал бы этим требованиям.

Разрешено ли компилятору заменять первый код вторым, даже если ему нужно копать рекурсивно в вызываемые функции? Могут ли современные компиляторы сделать это?

4b9b3361

Ответ 1

GCC имеет pure attribute (используется как __attribute__((pure))) для функций, которые сообщают компилятору, что избыточные вызовы могут быть устранены, Он используется, например, на strlen.

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

Ответ 2

Да, абсолютно.

Составители делают это все время и многое другое.

Например, если вся ваша функция была возвращена true, и ее определение было видимым для компилятора на call-сайте, то, вероятно, был бы удален весь вызов функции, в результате чего:

someObscureFunction(true, true);

Программа, для которой компилятор имеет достаточную информацию, может быть "оптимизирована" из довольно сложной цепочки задач до одной или двух инструкций. Теперь, действуя на переменные-члены, в некоторой степени подталкивает оптимизатор к своему пределу, но если переменные private, получают известное начальное значение и не мутируются какой-либо другой функцией-членом, я не понимаю, почему компилятор не мог просто установить его известное значение, если захочет. Компиляторы очень, очень умны.

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

Ответ 3

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

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

Другими словами, только потому, что метод класса const не означает, что он не имеет наблюдаемых побочных эффектов при его вызове.

Ответ 4

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

Ответ 5

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

Теперь вы задали вопрос о const - это в основном полезно для разработчика, а не для кодера. Функция const - это подсказки о том, что метод не изменяет объект, на который он вызван, а аргументы const - это подсказки о том, что аргументы не изменяются. Однако функция может (юридически 1) отбрасывать const -ness указателя this или его аргументов. Поэтому компилятор не может положиться на него.

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

Таким образом, объявления const помогают программисту, а не компилятору 2.

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

  • Функция может находиться в том же модуле компиляции, что и вызывающий, позволяя компилятору проверить его и точно определить, на что он опирается. Конечная форма этого заключается в следующем: тело функции может быть перемещено в вызывающий объект, после чего оптимизатор может удалить избыточный код из более поздних вызовов (вплоть до включения всего кода из этих вызовов целиком и, возможно, всех или портов исходного вызова тоже).
  • Инструментальная цепочка может использовать некоторый тип оптимизации времени соединения, что позволяет эффективно использовать тип анализа, описанный выше, даже для функций и вызывающих абонентов в разных единицах компиляции. Это может позволить эту оптимизацию для любого кода, присутствующего при создании окончательного исполняемого файла.
  • Компилятор может разрешить пользователю аннотировать функцию с атрибутом, который сообщает компилятору, что он может рассматривать функцию как не имеющую побочных эффектов. Например, gcc предоставляет атрибуты pure и const, которые сообщают gcc, что функции не имеют побочных эффектов и зависят только по их параметрам (и по глобальным переменным, в случае pure).

1 Обычно, пока объект изначально не был определен как const.

2 Существует один смысл, в котором определения const помогают компилятору: они могут помещать глобальные объекты, определенные как const в раздел только для чтения исполняемого файла (если такая функция существует) и также объединяют такие объекты, когда они равны (например, одинаковые строковые константы).