У меня есть класс, в котором я перегружен ==
оператор с memcmp()
на конкретного участника. Из-за плохой копии, сделанной в коде (memcpy
вызывается с большим размером, чем следует) У меня был segfault при вызове ==
оператор.
Я понимаю, что UB таинственен и явно не определен, но все же есть кое-что, что я заметил, что меня заинтриговало.
Во время отладки я поменял местами ==
вызов с его реализацией (т.е. a==b
поменялся с memcmp(a.member_x, b.member_x, SIZE)
) и никакого сегфо!
Итак, есть ли разница между использованием самого оператора и его заменой реализацией или это только UB?
Чтобы уточнить: да, этот код включает в себя UB. Это плохо, и его результаты не определены. То, что я хочу знать, это: происходит ли что-то другое при вызове оператора или при вызове его тела? UB просто заставил меня думать, что разница может существовать (и, очевидно, была исправлена)
Неопределенное поведение означает, что «все может случиться». «Все» включает в себя «работать так, как задумано». Это может означать, что вы можете получить другое поведение, не меняя ничего, и это может означать, что вы получаете то же самое поведение, даже если вы что-то изменили.
В прошлом предупреждения о необходимости полагаться на неопределенное поведение часто включали в себя общеизвестный «запуск ядерных ракет».
Однако с современными агрессивно оптимизирующими компиляторами поведение может быть намного более тонким. В прошлом неопределенное поведение обычно приводило к тому, «что бы ни случилось». Например. в вашем примере вы либо прочитали бы «мусор» в памяти, если у вас есть доступ к нему, либо segfault, если у вас нет. Но операция (т. Е. «Сравнить эти два фрагмента памяти») все равно произойдет.
Это больше не «гарантировано» (не то, что когда-либо мы любые гарантии, когда дело доходит до UB) с современными агрессивно оптимизирующими компиляторами. Компилятор больше не будет просто делать глупости.
В современных оптимизирующих компиляторах компилятор часто должен решить (или доказать), что определенная оптимизация безопасна, т. Е. Что она не изменяет наблюдаемое заданное поведение. А поскольку UB означает «все может произойти», это означает, что часть оптимизатора, которая доказывает, что определенные оптимизации безопасны, может «принять все, что захочет». По сути, он может предположить, что все оптимизации безопасны, и затем продолжить, однако он хочет обеспечить наиболее агрессивную возможную оптимизацию.
В результате UB гораздо менее предсказуем и гораздо менее очевиден, чем когда-то. Например, UB в одном месте программы может привести к тому, что оптимизатор оптимизирует что-то таким образом, что он изменяет поведение чего-то другого в другой части программы, которая каким-то образом связана с этим фрагментом кода (например, он вызывает его, или оба манипулируют одним и тем же состоянием).
Допустим, у нас есть два потока, управляющих общим изменяемым состоянием. Один из двух потоков показывает UB. Затем оптимизатор может решить, что этот поток не манипулировать состоянием («что-нибудь может произойти», помните?) и поскольку теперь он может доказать, что к состоянию будет когда-либо обращаться только один поток, он может оптимизировать все блокировки! [Примечание: я понятия не имею, делает ли это какой-либо компилятор в реальности, но это было бы разрешается!]
Вот еще один пример, чтобы продемонстрировать, что «все может случиться» действительно, действительно делает означает «что-нибудь»: давайте предположим, что есть две возможные оптимизации, которые могут быть применены в некотором коде выше стека, который вызывает ваш operator==
, Одна оптимизация действительна, только если компилятор может доказать, что operator==
всегда будет правдой. Другая оптимизация допустима только в том случае, если компилятор может доказать, что она всегда будет ложной. Это означает, конечно, что ни одна оптимизация не может быть применена, так как в целом ваш operator==
может вернуть либо true, либо false.
Но! У нас есть UB. Таким образом, компилятор может решить просто предположить, что это всегда будет так, и применить оптимизацию # 1. Или он может решить, что он всегда будет ложным, и применить оптимизацию # 2. Хорошо, достаточно справедливо. Тем не менее, он также может решить применить и то и другое оптимизация! Помните: «все может случиться». Не просто «все, что имеет смысл в соответствии с логической структурой спецификации C ++», а период «что угодно». Если компилятору нужно, чтобы что-то было истинным и ложным одновременно, он может предположить это при наличии UB.
Вы можете думать о современном оптимизирующем компиляторе как о попытке доказать теоремы о вашем коде, а затем применить оптимизацию на основе этих доказательств. И UB позволяет это доказать любой а также все теоремы.
Других решений пока нет …