Re[17]: Когда это наконец станет defined behavior?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.04.23 08:37
Оценка:
Здравствуйте, T4r4sB, Вы писали:

R>>Я думаю, тебе не помешало бы разобраться с такими понятиями как undefined behavior, unspecified behavior и implementation defined behavior.


TB>То, от чего меняется логика при оптимизации — это как раз undefined behavior.


Таки нет. Undefined — это когда от твоего нарушения может взорваться вообще всё и не только в этом месте. Unspecified — это когда что-то произойдёт на выбор из описанных вариантов, но что именно — на этапе написания зафиксировать нельзя; причём в стандарте все такие случаи имеют локальный эффект (например, порядок вычисления аргументов функции или операндов в a+b — это не повлияет уже на соседний оператор).
И вот такие вещи, как порядок вычисления аргументов функции, от оптимизаций могут меняться весьма значительно.

R>>То есть, порядок выполнения операций (ассоциативность) и порядок вычисления операндов (подвыражений) — это две разные и независимые вещи.


TB>Так вот, почему гцц ссыт убрать лишнее чтение из памяти?


Тут и банально, и нет.

С одной стороны, он не знает, что делает этот bar(), может ли он поменять ту переменную, которая тебе передана по ссылке как a.
Запиши вместо декларации bar(), например: int bar() { return 1; } и увидишь, что он соптимизировал оба чтения в одно: (gcc 9.4.0, -O2, убрал незначащее)

Z3fooRKi:
        movl    (%rdi), %eax
        leal    1(%rax,%rax), %eax
        ret

Видно, что bar() заинлайнилась, а чтение переменной "a" одно на оба случая.

А вот теперь фокус — подставляем барьер памяти в bar():

int bar() {                                                                    
  std::atomic_signal_fence(std::memory_order_acquire);                         
  return 1;                                                                    
}


И код получился вообще потрясающий:

_Z3fooRKi:
        movl    (%rdi), %edx
        movl    (%rdi), %eax
        leal    1(%rdx,%rax), %eax
        ret


Одну и ту же "a" читаем дважды, bar() как бы выполнилось между ними, хотя её результат подставлен после.

Вывод — срабатывал принцип "мы не знаем, что делает bar(), значит, он мог поменять память как угодно".

А вот что меня таки смущает — почему это происходит при том, что в аргументе было "const int& a", а не "int& a". Clang — точно так же. Это значит, что константность для них действует одинаково как "мы не имеем права это менять", но не действует как "это не может поменять кто-то снаружи", несмотря на то, что формально это const.

Вот это, скорее всего, очевидно, если правильно и дословно вкурить стандарт, но я сейчас не осилил вкурить его нужным образом. Тут если кто-то ещё прокомментирует, с разбором конкретных пунктов, будет полезно.

И, естественно, всё это ещё в контексте предположений о том, что компилятор не может переставить порядок выполнения сложений в этой последовательности. Вот этот момент меня тоже смущает, но, может, его тут уже разрешили (надо перечитать, но рассказ про левоассоциативность сложения может решать этот аспект).
The God is real, unless declared integer.
Отредактировано 29.04.2023 8:51 netch80 . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.