Что такое атрибуты [[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