Я говорю, что это правильно, основываясь на моем понимании pure и const , но если у кого-то есть точное определение двух, пожалуйста, говорите.Это сложно, потому что в документации GCC не указано, что означает, что функция не имеет «никаких эффектов, кроме возвращаемого значения» (для pure ) или «не проверяет никакие значения, кроме их аргументов»(для const ).Очевидно, что все функции имеют некоторые эффекты (они используют циклы процессора, изменяют память) и проверяют некоторые значения (код функции, константы).
"Побочные эффекты" будутдолжны быть определены в терминах семантики языка программирования C, но мы можем догадаться, что имеют в виду люди GCC, основываясь на назначении этих атрибутов, а именно на включении дополнительных оптимизаций (по крайней мере, для этого они и предполагаются).
Простите, если что-то из перечисленного является слишком базовым ...
Чистые функции могут участвовать в устранении общих подвыражений.Их особенность в том, что они не изменяют среду, поэтому компилятор может вызывать ее меньше раз без изменения семантики программы.
z = f(x);
y = f(x);
становится:
z = y = f(x);
Или полностью исключается, если z
и y
не используются.
Поэтому мое лучшее предположение состоит в том, что рабочее определение «pure» - это «любая функция, которую можно вызывать меньше раз без изменения семантикипрограмма».Однако вызовы функций не могут быть перемещены, например,
size_t l = strlen(str); // strlen is pure
*some_ptr = '\0';
// Obviously, strlen can't be moved here...
Const-функции могут быть переупорядочены, поскольку они не зависят от динамической среды.
// Assuming x and y not aliased, sin can be moved anywhere
*some_ptr = '\0';
double y = sin(x);
*other_ptr = '\0';
Таким образом, мое лучшее предположение состоит в том, что рабочим определением «const» является «любая функция, которая может быть вызвана в любой момент без изменения семантики программы».Однако существует опасность:
__attribute__((const))
double big_math_func(double x, double theta, double iota)
{
static double table[512];
static bool initted = false;
if (!initted) {
...
initted = true;
}
...
return result;
}
Поскольку это const, компилятор может переупорядочить его ...
pthread_mutex_lock(&mutex);
...
z = big_math_func(x, theta, iota);
...
pthread_mutex_unlock(&mutex);
// big_math_func might go here, if the compiler wants to
В этом случае он может быть вызван одновременно из двух процессоров, дажехотя он появляется только внутри критического раздела в вашем коде.Тогда процессор мог бы решить отложить изменения до table
после того, как изменение до initted
уже прошло, что является плохой новостью.Вы можете решить эту проблему с помощью барьеров памяти или pthread_once
.
Я не думаю, что эта ошибка когда-либо будет отображаться на x86, и я не думаю, что она появляется на многих системах, которые не имеют несколькихфизические процессоры (не ядра).Так что он будет прекрасно работать целую вечность, а затем внезапно выйдет из строя на компьютере POWER с двумя сокетами.
Вывод: Преимущество этих определений состоит в том, что они дают понять, какие изменения вносит компиляторразрешено вносить при наличии этих атрибутов, что (как мне кажется) несколько расплывчато в документах GCC.Недостатком является то, что неясно, что это определения, используемые командой GCC.
Если вы посмотрите, например, на спецификацию языка Haskell, вы найдете гораздо более точное определение чистоты, поскольку чистотаочень важен для языка Haskell.
Редактировать: Я не смог заставить GCC или Clang переместить отдельный вызов функции __attribute__((const))
через другой вызов функции, но это кажется полностьюВозможно, что в будущем что-то подобное случится.Помните, когда -fstrict-aliasing
стал значением по умолчанию, и у всех внезапно появилось намного больше ошибок в их программах? Подобные вещи заставляют меня быть осторожнее.
Мне кажется, что когда вы отмечаете функцию __attribute__((const))
, вы обещаете компилятору, что результат вызова функции будет таким женезависимо от того, когда он вызывается во время выполнения вашей программы, при условии, что параметры одинаковы.
Тем не менее, я нашел способ вывести функцию const из критической секции, хотяЭто можно было бы назвать обманом.
__attribute__((const))
extern int const_func(int x);
int func(int x)
{
int y1, y2;
y1 = const_func(x);
pthread_mutex_lock(&mutex);
y2 = const_func(x);
pthread_mutex_unlock(&mutex);
return y1 + y2;
}
Компилятор переводит это в следующий код (из сборки):
int func(int x)
{
int y;
y = const_func(x);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
return y * 2;
}
Обратите внимание, что этого не произойдет только с __attribute__((pure))
, атрибутом const
и только атрибутом const
, вызывающим такое поведение.
Как видите, звонок внутри критической секции исчез. Кажется довольно произвольным, что предыдущий вызов был сохранен, и я не хотел бы заключить пари, что компилятор в какой-то будущей версии не примет иного решения о том, какой вызов оставить, или может ли он переместить вызов функции куда-нибудь остальное целиком.
Заключение 2: Действуйте осторожно, потому что, если вы не знаете, какие обещания вы даете компилятору, будущая версия компилятора может вас удивить.