Почему компиляторы С++ не оптимизируют это условное логическое назначение как безусловное присваивание?

Рассмотрим следующую функцию:

void func(bool& flag)
{
    if(!flag) flag=true;
}

Мне кажется, что если флаг имеет допустимое логическое значение, это будет равносильно безусловному его установке на true, например:

void func(bool& flag)
{
    flag=true;
}

Однако ни gcc, ни clang не оптимизируют его таким образом - оба генерируют следующее на уровне оптимизации -O3:

_Z4funcRb:
.LFB0:
    .cfi_startproc
    cmp BYTE PTR [rdi], 0
    jne .L1
    mov BYTE PTR [rdi], 1
.L1:
    rep ret

Мой вопрос: это просто, что код слишком особенный, чтобы оптимизировать или есть веские причины, почему такая оптимизация была бы нежелательной, учитывая, что flag не является ссылкой на volatile? Кажется, единственной причиной может быть то, что flag может каким-то образом иметь значение true -or- false без undefined поведения в точке его чтения, но я не уверен, что это возможно.

Ответ 1

Это может отрицательно сказаться на производительности программы из-за соображений когерентности кэша. Запись на flag каждый раз, когда вызывается func(), заглушает содержащуюся строку кэша. Это произойдет независимо от того, что записываемое значение точно соответствует битам, найденным на целевом адресе перед записью.


ИЗМЕНИТЬ

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

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

const bool foo = true;

int main()
{
    func(const_cast<bool&>(foo));
}

При безусловной записи в func() это определенно вызывает поведение undefined (запись в память только для чтения завершает работу программы, даже если эффект записи в противном случае был бы не-op).

Ответ 2

Помимо ответа Леона на производительность:

Предположим, что flag - true. Предположим, что два потока постоянно вызывают func(flag). Функция, написанная в этом случае, ничего не хранит в flag, поэтому это должно быть поточно-безопасным. Два потока имеют доступ к одной и той же памяти, но только для чтения. Безусловная установка flag в true означает, что два разных потока будут записывать в одну и ту же память. Это небезопасно, это небезопасно, даже если записываемые данные идентичны уже имеющимся данным.

Ответ 3

Я не уверен в поведении С++ здесь, но в C память может измениться, потому что если память содержит ненулевое значение, отличное от 1, оно останется неизменным с проверкой, но изменится на 1 с проверкой.

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