На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> UB или нет
    Навеяно одной дискуссией. Непонятно, есть ли UB в коде или нет.
    ExpandedWrap disabled
      auto diff = valid_ptr - nullptr;
      auto x = nullptr + diff;
      std::cout << (x == valid_ptr); // гарантирует ли стандарт, что тут будет выведено true? Если нет, то почему?
      std::cout << *x;               // Если ответ на прошлый вопрос "да", то есть ли тут UB?
      Если быстро, т.к. некогда лезть в Стандарт, то не должно скомпилиться. nullptr – это не указатель, это значение специального типа std::nullptr_t, причём единственное в нём, других не существует. Адресная арифметика с ним, вроде бы, не определена, вот в этом не уверен, не помню. Скорее всего, т.к. его равнопредпочтительно можно привести и к указателю, и к целому, а это сильно разные операции в адресной арифметике.

      Добавлено
      • Если откастуешь к целому, то auto diff будет указателем и не скомпилится вторая строка. Кроме как nullptr откастуешь там к целому.
      • Если откасуешь к указателю, то UB ещё в первой строке, т.к. адресная арифметика не определена на масштабах, выходящих за рамки одного массива объектов. (Отдельный объект равносилен массиву из одного элемента.)


      Добавлено
      Подозреваю, что в строках 1 и 2 предполагаются разные касты. Не?
      Сообщение отредактировано: Qraizer -
        Цитата Qraizer @
        nullptr – это не указатель, это значение специального типа std::nullptr_t

        Да, точно, я на коленке придумал пример. Предполагается, что nullptr кастуем к decltype(valid_ptr), valid_ptr - некий указатель на объект.
          Интересно, что скажет Qraizer на следующий тезис?
          Цитата
          Вообще-то, внезапно, все оптимизации (ну или почти все) опираются на UB. То есть буквально, я не могу, навскидку, придумать никакой оптимизации C кода, которая бы не ломала какую-либо программу с UB.
            Цитата Qraizer @
            Если откасуешь к указателю, то UB ещё в первой строке, т.к. адресная арифметика не определена на масштабах, выходящих за рамки одного массива объектов. (Отдельный объект равносилен массиву из одного элемента.)

            А, понятно тогда.
              Цитата applegame @
              Интересно, что скажет Qraizer на следующий тезис?
              Бессмысленный тезис. Если это перевод, то паршивый. Правильно, как я могу предположить, "все оптимизации имеют право положить на любые предположения, когда встречают UB".

              Добавлено
              Как пример. Есть одно правило. В вольном "переводе" оно требует, чтобы в программе не было контекстов, где указатели (без учёта cv-квалификации) разных типов, кроме как один из них void*, ссылались бы на один и тот же объект или его подобъекты. Это даёт хорошие варианты оптимизатору, при которых программист маловероятно, что ненароком что-то сломает. Например:
              ExpandedWrap disabled
                void f(int* ptr1, char* ptr2)
              Тут оптимизатор имеет право считать, что ptr1 и ptr2 гарантировано указывают на разные объекты и оптимизировать подссыльные им объекты исходя из этого. Конечно, если программер вызовет такую функцию как
              ExpandedWrap disabled
                extern unsigned char buffer[];
                 
                f((int*)buffer, (char*)buffer);
              пенять на оптимизатор будет неправильно. Но вот тут
              ExpandedWrap disabled
                void f(int* ptr1, int* ptr2)
              такого предположения оптимизатор делать уже не вправе, и поэтому оптимизация тех же объектов будет значительно консервативнее, и программер с проблемами столкнуться не должен.

              Добавлено
              P.S. В C99 ввели restricted для поинтеров как раз с целью упростить жизнь. Но практика показала, что это не работает. Лишь в малом количестве случаев компилятор способен обнаружить нарушения, а программеры часто всё равно ошибаются, предоставляя гарантии, которых на самом деле нет. Так что Плюсы остались без оного нововведения.

              Добавлено
              P.P.S. "Имеют право" не означает "обязательно будут", естественно.
              Сообщение отредактировано: Qraizer -
                Цитата Qraizer @
                Бессмысленный тезис. Если это перевод, то паршивый. Правильно, как я могу предположить, "все оптимизации имеют право положить на любые предположения, когда встречают UB".
                Это не перевод. Автор просто несколько косноязычный. Перефразирую то, о чем был спор.
                Что будет если мы изменим текущее поведение компилятора. И разрешим ему выполнять конкретную оптимизацию только в тех случаях когда компилятор способен доказать, что эта оптимизация никак не меняет результат работы программы по сравнению с неоптимизированной версией (ну кроме времени выполнения, конечно). Если компилятор не в состоянии это доказать, то он обязан не делать эту оптимизацию. Допустим если компилятор встретил чтение из неинициализированной переменной, то ему запрещено делать любые предположения, и предписывается реально читать результат из этой переменной.
                Автор тезиса утверждает, что такой компилятор C/C++ вообще почти ничего не сможет оптимизировать.
                Сообщение отредактировано: applegame -
                  Возвращаясь к тезису, я даже больше скажу. UB в Стандарте и присутствует как раз для того, чтобы максимально развязать руки оптимизаторам. Всё, что может их поломать, отнесено к UB. Увы, мы хотим, чтобы компилятор, когда строит отображение виртуальной исполнительной C/C++ машины на реальный процессор, всё сделал за нас, исходя из его знаний о нём и его особенностях. К сожалению, компилятор видит лишь то, что мы ему написали, а не то, что мы имели в голове, когда писали, и ему категорически сложно бывает нас понять. Есть языки, которые позволяют многое рассказать компилятору о нашим намерениях, например Ада. Там заманаешься объяснять компилеру каждый свой чих, и нам это также не нравится, как и полная анархия. Плюсы давно уже идут по пути декларативности намерений, буквально с первого Стандарта. Найти золотую середину между чёткой формализацией своих идей и полнотой свободы компилятору бывает непросто.

                  Добавлено
                  Цитата applegame @
                  Это не перевод. Автор просто несколько косноязычный. Перефразирую то, о чем был спор.
                  Похоже, я заглянул в твой ещё не написанный пост.

                  Добавлено
                  Цитата applegame @
                  Автор тезиса утверждает, что такой компилятор C/C++ вообще почти ничего не сможет оптимизировать.
                  Он близок к истине. Не настолько категорично, но да, компиляторы вернутся в начало 90-ых.
                  В догонку, если вы там ещё спорите, то могу посоветовать в качестве контраргумента то, что понятие доказательства является довольно нечётким термином, и что те оптимизации, которые мы сейчас имеем на стороне фронт-энда, являются строго и математически выведенными и показаны их безопасность. (За бак-энд оптимизацию говорить, думаю, тут будет вне контекста, мы ж не ассемблер обсуждаем.) Так что пусть расслабится, уже "всё" доказано до нас. Но за это надо платить соблюдением формальных правил, которые требуют не впадать в UB, иначе вся математика летит к чертям.
                  Сообщение отредактировано: Qraizer -
                    Цитата Qraizer @
                    Возвращаясь к тезису, я даже больше скажу. UB в Стандарте и присутствует как раз для того, чтобы максимально развязать руки оптимизаторам.
                    Так а если убрать UB из Стандарта, то насколько сильно это свяжет руки оптимизаторам? Действительно ли намертво свяжет и -O3 практически перестанет отличаться от -O0?
                    Я склонен считать, что современные компиляторы и их бэкенды достаточно мощны, чтобы в среднестатистической программе в большинстве случаев распознавать отсутствие неопределенностей и включать оптимизацию на полную.
                    То бишь я считаю что введение UB было актуально много лет назад, когда компиляторы и компьютеры были относительно слабыми для таких фокусов.

                    Добавлено
                    Более того, я считаю, что большинство оптимизаций основанных на предположении отсутствия UB еще и совершенно бесполезны. Примеры сейчас приведу.
                      Цитата applegame @
                      Если компилятор не в состоянии это доказать, то он обязан не делать эту оптимизацию.
                      А в целом, компиляторы и так не делают много из того, что могли бы, если б не их опасения о нас родимых. Скажем, ссылки позволяют компилятору куда больше, чем указатели на их месте.

                      Добавлено
                      Цитата applegame @
                      Так а если убрать UB из Стандарта, то насколько сильно это свяжет руки оптимизаторам? Действительно ли намертво свяжет и -O3 практически перестанет отличаться от -O0?
                      Сильно свяжет. Ещё раз: мы же хотим, чтобы компилятор всё сделал за нас. Нынче мы получаем от них объектный код, который в большинстве случаев идеален или недалёк от него для этого типа процессоров. За счёт чего, как думаешь? Риторический вопрос.

                      Добавлено
                      И вообще, если б мы хотели более полной свободы в самовыражении своих идей, оставив всю заботу о технических аспектах компилятору, мы б просто взяли другой язык. Не так ли?
                        Пример со сверткой констант:
                        ExpandedWrap disabled
                          static int foo(int m, int n) {
                              if (m == 0) return n + 1;
                              if (n == 0) return foo(m - 1, 1);
                              return foo(m - 1, n - 1);
                          }
                           
                          int main() {
                              int n = 1;
                              *const_cast<int*>(&n) = foo(10, 12);
                              return n;
                          }

                        Компилятор на -O3 отлично сворачивает константу и программа сводится к
                        ExpandedWrap disabled
                          main:
                                  mov     eax, 3
                                  ret

                        А теперь добавляем немножко UB:
                        ExpandedWrap disabled
                          static int foo(int m, int n) {
                              if (m == 0) return n + 1;
                              if (n == 0) return foo(m - 1, 1);
                              return foo(m - 1, n - 1);
                          }
                           
                          int main() {
                              const int n = 1;
                              *const_cast<int*>(&n) = foo(10, 12);
                              return n;
                          }

                        И, конечно же, компилятор разрешая программисту модифицировать константу (а он отлично об этом знает, судя по первому примеру), игнорирует эту модификацию:
                        ExpandedWrap disabled
                          main:
                                  mov     eax, 1
                                  ret

                        Я конечно понимаю, что компилятор имеет право так делать. UB - что хочу то и возвращаю. Но я не понимаю в чем смысл этой оптимизации? Она не дает никакого прироста в скорости и с вероятностью 99.9999% делает не то, что ожидает программист. Какова цель разрешать программисту писать хаки, но при этом не выполнять их?

                        Добавлено
                        Цитата Qraizer @
                        И вообще, если б мы хотели более полной свободы в самовыражении своих идей, оставив всю заботу о технических аспектах компилятору, мы б просто взяли другой язык. Не так ли?
                        Так в том-то и дело, что компилятор своими бесполезными UB-оптимизациями режет нам эту свободу. Вот мне надо модифицировать константу, и все тут. А компилятор показывает нам кукиш. Причем делает это молча, без возражений.
                        А ведь мог бы нормально соптимизировать не опираясь на понятие UB, как в первом примере.
                        То есть отсутвие опоры на UB ни разу не мешает компилятору свернуть константу, просто вот так захотелось разработчикам компилятора. Экономят время компиляции что ли?
                        Сообщение отредактировано: applegame -
                          Дай-ка перефразирую: ты выступаешь за то, чтобы семантика языковых конструкций зависела от контекста? :o Прости, но комментировать я это желание не буду. Как и желание модифицировать константу. Увидев подобный код, намерений его автора даже человек не поймёт, не то что бездушный компилятор. А спрашивать и не буду, завтра автора за соседним столом не окажется, уволят без пособия.
                          Сообщение отредактировано: Qraizer -
                            Цитата Qraizer @
                            Дай-ка перефразирую: ты выступаешь за то, чтобы семантика языковых конструкций зависела от контекста?
                            Нет, я топлю за вредоносность UB и оптимизации на них опирающиеся. Число UB от версии к версии компилятора должно уменьшаться, а не расти.
                            Цитата Qraizer @
                            Как и желание модифицировать константу. Увидев подобный код, намерений его автора даже человек не поймёт, не то что бездушный компилятор. А спрашивать и не буду, завтра автора за соседним столом не окажется, уволят без пособия.
                            Так дело то не в качестве этого кода. А в отсутствии здравого смысла (с точки зрения программиста) в поведении компилятора. В данном случае он никак не помогает программисту, а только мешает. Надо либо вообще запретить снимать модификатор const и тем самым исключить случаи модификации констант, либо раз уж C++ такой свободный язык позволяющий все что угодно, то модифицировать константу как написал программист. В общем конкретизировать поведение или объявить его не UB, а IB.
                            Сообщение отредактировано: applegame -
                              Цитата applegame @
                              Надо либо вообще запретить снимать модификатор const и тем самым исключить случаи модификации констант,
                              "Не пойдёть!". Яркий пример - константы во флеш-памяти микроконтроллера, которые простой записью в ячнйку изменить невозможно физически. Однако, во время обновления программы, после некоторых шаманских действий, она записывается именно записью в ячейку. Или код, который подготавливает окружение перед запуском main() и инциализирует те самые const-переменные, тоже может быть сам написан на сях или плюсах.
                                Мульён примеров можно придумать. Вот был код в ПЗУ. Часть его .bss секций была априори константной. ОЗУ использовалась консервативно, только для данных и стека. Потом решили повысить производительность кода, в связи чем перенести код тоже в ОЗУ. Чуток подправили harness модуль, и бац! секция .bss перестала быть константной.
                                Или берём наши железки, что к стенду прикручены. Их начальный загрузчик умеет три варианта: принять исполняемый образ прошивки по RS-у, режим разработчика; считать код приложения из флеш в ОЗУ и активировать JTAG, отладочный режим; передать управление прямо на флеш, боевой режим. Режимы управляются перемычками. В боевом режиме ко всему почему арбитр шины отслеживает несанкционированные попытки изменить флеш (речь о высококритичном софте) и генерит процессору критический эксепшн.
                                Что тут компилятор наоптимизирует? Реализацией поведение не регламентируется, это настоящее UB.
                                Цитата applegame @
                                Надо либо вообще запретить снимать модификатор const и тем самым исключить случаи модификации констант ...
                                const не является частью характеристики объекта, на то он и модификатор типа, а не часть определения типа. const, как и volatile, может являться частью контракта его интерфейса. У тебя объект может быть самым обычным, но наружу ты его экспортируешь как константный. Или ни разу не видел, как методы классов возвращают константные ссылки на свои изменчивые атрибуты, репрезентуемые обычными полями? Если такая константа приходит тебе обратно от пользователя, ты имеешь полно право снять с него константность, если вдруг приспичило. Стандарт чётко определяет UB только при модификации исходно константных объектов, для того же поля, которые сразу определены в классе как const, например. Для тех же, на кого константность навешана контрактом интерфейса, но константами в точке определения не являются, никакого UB нет.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0554 ]   [ 16 queries used ]   [ Generated: 16.04.24, 17:24 GMT ]