Что такое атрибуты [[reproducible]] и [[unsequenced]] в C23 и когда мне следует их использовать?

C23 представил атрибуты[[reproducible]]и[[unsequenced]].

  • Какова их мотивация?
  • Как они определяются и какое влияние они оказывают на функцию?
  • К каким функциям мне следует их применять?

1 ответ

Мотивирующая проблема заключается в том, что компилятор не имеет представления о функциях, если нет определения функции. Это предотвращает почти все оптимизации компилятора (если не используются LTO).

Рассмотрим следующий пример:

      // note: this is redundant, because [[unsequenced]] implies [[reproducible]]
int square(int x) [[reproducible]] [[unsequenced]];

int arr[] = {
    square(2),
    square(2),
    square(3),
    square(3),
};

Несмотря на то, что в компиляторе нет определения , разрешено выполнить две оптимизации:

  • Поскольку функция , дает один и тот же результат при вызове дважды подряд, и компилятор может принять решение о вызове только один раз.
  • Поскольку функция , вызываетsquareмогут быть выполнены в любом порядке, и компилятор может даже решить выполнить оценку только один раз при запуске программы. Он также может принять решение об оценкеsquare(3)доsquare(2), если это как-то эффективнее.

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

Полуформальные определения

Более подробное объяснение см. в рабочем проекте стандарта C23 N3096 ​​§6.7.12.7 Стандартные атрибуты для типов функций.

Этот атрибут утверждает, что функция является воспроизводимой функцией , что означает, что

  • это бесполезно и
  • это идемпотент

Effectless ограничивает то, какое состояние может изменить функция. Если какое-либо нелокальное состояние изменено, это может произойти только посредством переданных ему указателей. Например,void to_upper_case(char *str)функция бесполезна , если она изменяет только локальные переменные и содержимоеstr. (Интуитивно понятно, что функция не имеет заметных побочных эффектов.)

Идемпотентность означает, что вызов функции несколько раз имеет тот же эффект, что и один ее вызов. Например, мы можем позвонитьto_upper_case(s); to_upper_case(s);, и это будет иметь тот же эффект, что и вызов только один раз.

Этот атрибут утверждает, что функция является неупорядоченной функцией , что означает, что

  • он бесэффектен и идемпотентен (что также делает его воспроизводимым )
  • это лицо без гражданства
  • это независимо

Безгражданство означает, чтоstaticилиthread_localлокальные переменные не могут быть не- и не могут бытьvolatile.

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

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

      char *str = /* ... */;  // A
  strlen(str);
  global = 123;
  strlen(str);
strcpy(str, /* ... */); // B

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

Примечание об атрибутах GCC

Атрибуты GCCиявляются источником вдохновения для этих стандартных атрибутов и ведут себя аналогичным образом. См. N2956 5.8 Некоторые различия с GCC const и pure для сравнения. Суммируя:

  • pureболее расслаблен, чем[[independent]]
  • constявляется более строгим, чем

Когда использовать эти атрибуты

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

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

Распространенные примеры (и сюрпризы)

  • printfочевидно, ни то, ни другое
  • strlenиmemcmpможет быть (Может ли strlen быть [[unsequenced]]?)
  • memcpyвозможно[[reproducible]]
  • memmoveтоже не может быть, потому что он не идемпотентен для перекрывающихся областей памяти
  • fabsвозможно[[unsequenced]]
  • sqrtтоже не может быть, поскольку он изменяет среду с плавающей запятой и может установитьerrno

Смотрите также

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