Функции GCC pure/const, которые принимают аргумент указателя

Может кто-то прояснит, может ли (и почему) функция присваиваться pure или const, если у нее есть указательный параметр.

В соответствии с документацией GCC:

Некоторые из общих примеров чистых функций - это strlen или memcmp.

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

например:

char *x = calloc(1, 8);
char *y = calloc(1, 8);

if (memcmp(x, y, 8) > 0)
    printf("x > y\n");

x[1] = 'a';
if (memcmp(x, y, 8) > 0)
    printf("x > y\n");

Параметры второго вызова memcmp идентичны первому (указатели указывают на один и тот же адрес), как компилятор знает, не использовать результат от первого вызова, если memcmp является чистым?

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

Ответы

Ответ 1

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

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

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

Пример из статьи выглядит следующим образом:

int someimpurefunction(int a);
int somepurefunction(int a)
  __attribute__((pure));

int testfunction(int a, int b, int c, int d) {

  int res1 = someimpurefunction(a) ? someimpurefunction(a) : b;
  int res2 = somepurefunction(a) ? somepurefunction(a) : c;
  int res3 = a+b ? a+b : d;

  return res1+res2+res3;
}

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

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

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

x[1] = 'a';

он не может устранить второй вызов memcmp, поскольку память, на которую указывает x, изменилась.

Ответ 2

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

В вашем примере кода memcmp может быть функцией pure. Компилятор видит, что память изменяется между вызовами на memcmp и не может повторно использовать результат первого вызова для второго вызова.

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

По этой причине представляется безопасным объявлять функцию, которую вы хотите реализовать как pure (но не const).