Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.220.137.164] |
|
Сообщ.
#1
,
|
|
|
Навеяно одной дискуссией. Непонятно, есть ли UB в коде или нет.
auto diff = valid_ptr - nullptr; auto x = nullptr + diff; std::cout << (x == valid_ptr); // гарантирует ли стандарт, что тут будет выведено true? Если нет, то почему? std::cout << *x; // Если ответ на прошлый вопрос "да", то есть ли тут UB? |
Сообщ.
#2
,
|
|
|
Если быстро, т.к. некогда лезть в Стандарт, то не должно скомпилиться. nullptr – это не указатель, это значение специального типа std::nullptr_t, причём единственное в нём, других не существует. Адресная арифметика с ним, вроде бы, не определена, вот в этом не уверен, не помню. Скорее всего, т.к. его равнопредпочтительно можно привести и к указателю, и к целому, а это сильно разные операции в адресной арифметике.
Добавлено Добавлено Подозреваю, что в строках 1 и 2 предполагаются разные касты. Не? |
Сообщ.
#3
,
|
|
|
Цитата Qraizer @ nullptr – это не указатель, это значение специального типа std::nullptr_t Да, точно, я на коленке придумал пример. Предполагается, что nullptr кастуем к decltype(valid_ptr), valid_ptr - некий указатель на объект. |
Сообщ.
#4
,
|
|
|
Интересно, что скажет Qraizer на следующий тезис?
Цитата Вообще-то, внезапно, все оптимизации (ну или почти все) опираются на UB. То есть буквально, я не могу, навскидку, придумать никакой оптимизации C кода, которая бы не ломала какую-либо программу с UB. |
Сообщ.
#5
,
|
|
|
Цитата Qraizer @ Если откасуешь к указателю, то UB ещё в первой строке, т.к. адресная арифметика не определена на масштабах, выходящих за рамки одного массива объектов. (Отдельный объект равносилен массиву из одного элемента.) А, понятно тогда. |
Сообщ.
#6
,
|
|
|
Цитата applegame @ Бессмысленный тезис. Если это перевод, то паршивый. Правильно, как я могу предположить, "все оптимизации имеют право положить на любые предположения, когда встречают UB". Интересно, что скажет Qraizer на следующий тезис? Добавлено Как пример. Есть одно правило. В вольном "переводе" оно требует, чтобы в программе не было контекстов, где указатели (без учёта cv-квалификации) разных типов, кроме как один из них void*, ссылались бы на один и тот же объект или его подобъекты. Это даёт хорошие варианты оптимизатору, при которых программист маловероятно, что ненароком что-то сломает. Например: void f(int* ptr1, char* ptr2) extern unsigned char buffer[]; f((int*)buffer, (char*)buffer); void f(int* ptr1, int* ptr2) Добавлено P.S. В C99 ввели restricted для поинтеров как раз с целью упростить жизнь. Но практика показала, что это не работает. Лишь в малом количестве случаев компилятор способен обнаружить нарушения, а программеры часто всё равно ошибаются, предоставляя гарантии, которых на самом деле нет. Так что Плюсы остались без оного нововведения. Добавлено P.P.S. "Имеют право" не означает "обязательно будут", естественно. |
Сообщ.
#7
,
|
|
|
Цитата Qraizer @ Это не перевод. Автор просто несколько косноязычный. Перефразирую то, о чем был спор.Бессмысленный тезис. Если это перевод, то паршивый. Правильно, как я могу предположить, "все оптимизации имеют право положить на любые предположения, когда встречают UB". Что будет если мы изменим текущее поведение компилятора. И разрешим ему выполнять конкретную оптимизацию только в тех случаях когда компилятор способен доказать, что эта оптимизация никак не меняет результат работы программы по сравнению с неоптимизированной версией (ну кроме времени выполнения, конечно). Если компилятор не в состоянии это доказать, то он обязан не делать эту оптимизацию. Допустим если компилятор встретил чтение из неинициализированной переменной, то ему запрещено делать любые предположения, и предписывается реально читать результат из этой переменной. Автор тезиса утверждает, что такой компилятор C/C++ вообще почти ничего не сможет оптимизировать. |
Сообщ.
#8
,
|
|
|
Возвращаясь к тезису, я даже больше скажу. UB в Стандарте и присутствует как раз для того, чтобы максимально развязать руки оптимизаторам. Всё, что может их поломать, отнесено к UB. Увы, мы хотим, чтобы компилятор, когда строит отображение виртуальной исполнительной C/C++ машины на реальный процессор, всё сделал за нас, исходя из его знаний о нём и его особенностях. К сожалению, компилятор видит лишь то, что мы ему написали, а не то, что мы имели в голове, когда писали, и ему категорически сложно бывает нас понять. Есть языки, которые позволяют многое рассказать компилятору о нашим намерениях, например Ада. Там заманаешься объяснять компилеру каждый свой чих, и нам это также не нравится, как и полная анархия. Плюсы давно уже идут по пути декларативности намерений, буквально с первого Стандарта. Найти золотую середину между чёткой формализацией своих идей и полнотой свободы компилятору бывает непросто.
Добавлено Цитата applegame @ Похоже, я заглянул в твой ещё не написанный пост. Это не перевод. Автор просто несколько косноязычный. Перефразирую то, о чем был спор. Добавлено Цитата applegame @ Он близок к истине. Не настолько категорично, но да, компиляторы вернутся в начало 90-ых.Автор тезиса утверждает, что такой компилятор C/C++ вообще почти ничего не сможет оптимизировать. В догонку, если вы там ещё спорите, то могу посоветовать в качестве контраргумента то, что понятие доказательства является довольно нечётким термином, и что те оптимизации, которые мы сейчас имеем на стороне фронт-энда, являются строго и математически выведенными и показаны их безопасность. (За бак-энд оптимизацию говорить, думаю, тут будет вне контекста, мы ж не ассемблер обсуждаем.) Так что пусть расслабится, уже "всё" доказано до нас. Но за это надо платить соблюдением формальных правил, которые требуют не впадать в UB, иначе вся математика летит к чертям. |
Сообщ.
#9
,
|
|
|
Цитата Qraizer @ Так а если убрать UB из Стандарта, то насколько сильно это свяжет руки оптимизаторам? Действительно ли намертво свяжет и -O3 практически перестанет отличаться от -O0?Возвращаясь к тезису, я даже больше скажу. UB в Стандарте и присутствует как раз для того, чтобы максимально развязать руки оптимизаторам. Я склонен считать, что современные компиляторы и их бэкенды достаточно мощны, чтобы в среднестатистической программе в большинстве случаев распознавать отсутствие неопределенностей и включать оптимизацию на полную. То бишь я считаю что введение UB было актуально много лет назад, когда компиляторы и компьютеры были относительно слабыми для таких фокусов. Добавлено Более того, я считаю, что большинство оптимизаций основанных на предположении отсутствия UB еще и совершенно бесполезны. Примеры сейчас приведу. |
Сообщ.
#10
,
|
|
|
Цитата applegame @ А в целом, компиляторы и так не делают много из того, что могли бы, если б не их опасения о нас родимых. Скажем, ссылки позволяют компилятору куда больше, чем указатели на их месте. Если компилятор не в состоянии это доказать, то он обязан не делать эту оптимизацию. Добавлено Цитата applegame @ Сильно свяжет. Ещё раз: мы же хотим, чтобы компилятор всё сделал за нас. Нынче мы получаем от них объектный код, который в большинстве случаев идеален или недалёк от него для этого типа процессоров. За счёт чего, как думаешь? Риторический вопрос. Так а если убрать UB из Стандарта, то насколько сильно это свяжет руки оптимизаторам? Действительно ли намертво свяжет и -O3 практически перестанет отличаться от -O0? Добавлено И вообще, если б мы хотели более полной свободы в самовыражении своих идей, оставив всю заботу о технических аспектах компилятору, мы б просто взяли другой язык. Не так ли? |
Сообщ.
#11
,
|
|
|
Пример со сверткой констант:
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 отлично сворачивает константу и программа сводится к main: mov eax, 3 ret А теперь добавляем немножко UB: 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; } И, конечно же, компилятор разрешая программисту модифицировать константу (а он отлично об этом знает, судя по первому примеру), игнорирует эту модификацию: main: mov eax, 1 ret Я конечно понимаю, что компилятор имеет право так делать. UB - что хочу то и возвращаю. Но я не понимаю в чем смысл этой оптимизации? Она не дает никакого прироста в скорости и с вероятностью 99.9999% делает не то, что ожидает программист. Какова цель разрешать программисту писать хаки, но при этом не выполнять их? Добавлено Цитата Qraizer @ Так в том-то и дело, что компилятор своими бесполезными UB-оптимизациями режет нам эту свободу. Вот мне надо модифицировать константу, и все тут. А компилятор показывает нам кукиш. Причем делает это молча, без возражений.И вообще, если б мы хотели более полной свободы в самовыражении своих идей, оставив всю заботу о технических аспектах компилятору, мы б просто взяли другой язык. Не так ли? А ведь мог бы нормально соптимизировать не опираясь на понятие UB, как в первом примере. То есть отсутвие опоры на UB ни разу не мешает компилятору свернуть константу, просто вот так захотелось разработчикам компилятора. Экономят время компиляции что ли? |
Сообщ.
#12
,
|
|
|
Дай-ка перефразирую: ты выступаешь за то, чтобы семантика языковых конструкций зависела от контекста? Прости, но комментировать я это желание не буду. Как и желание модифицировать константу. Увидев подобный код, намерений его автора даже человек не поймёт, не то что бездушный компилятор. А спрашивать и не буду, завтра автора за соседним столом не окажется, уволят без пособия.
|
Сообщ.
#13
,
|
|
|
Цитата Qraizer @ Нет, я топлю за вредоносность UB и оптимизации на них опирающиеся. Число UB от версии к версии компилятора должно уменьшаться, а не расти.Дай-ка перефразирую: ты выступаешь за то, чтобы семантика языковых конструкций зависела от контекста? Цитата Qraizer @ Так дело то не в качестве этого кода. А в отсутствии здравого смысла (с точки зрения программиста) в поведении компилятора. В данном случае он никак не помогает программисту, а только мешает. Надо либо вообще запретить снимать модификатор const и тем самым исключить случаи модификации констант, либо раз уж C++ такой свободный язык позволяющий все что угодно, то модифицировать константу как написал программист. В общем конкретизировать поведение или объявить его не UB, а IB. Как и желание модифицировать константу. Увидев подобный код, намерений его автора даже человек не поймёт, не то что бездушный компилятор. А спрашивать и не буду, завтра автора за соседним столом не окажется, уволят без пособия. |
Сообщ.
#14
,
|
|
|
Цитата applegame @ "Не пойдёть!". Яркий пример - константы во флеш-памяти микроконтроллера, которые простой записью в ячнйку изменить невозможно физически. Однако, во время обновления программы, после некоторых шаманских действий, она записывается именно записью в ячейку. Или код, который подготавливает окружение перед запуском main() и инциализирует те самые const-переменные, тоже может быть сам написан на сях или плюсах. Надо либо вообще запретить снимать модификатор const и тем самым исключить случаи модификации констант, |
Сообщ.
#15
,
|
|
|
Мульён примеров можно придумать. Вот был код в ПЗУ. Часть его .bss секций была априори константной. ОЗУ использовалась консервативно, только для данных и стека. Потом решили повысить производительность кода, в связи чем перенести код тоже в ОЗУ. Чуток подправили harness модуль, и бац! секция .bss перестала быть константной.
Или берём наши железки, что к стенду прикручены. Их начальный загрузчик умеет три варианта: принять исполняемый образ прошивки по RS-у, режим разработчика; считать код приложения из флеш в ОЗУ и активировать JTAG, отладочный режим; передать управление прямо на флеш, боевой режим. Режимы управляются перемычками. В боевом режиме ко всему почему арбитр шины отслеживает несанкционированные попытки изменить флеш (речь о высококритичном софте) и генерит процессору критический эксепшн. Что тут компилятор наоптимизирует? Реализацией поведение не регламентируется, это настоящее UB. Цитата applegame @ const не является частью характеристики объекта, на то он и модификатор типа, а не часть определения типа. const, как и volatile, может являться частью контракта его интерфейса. У тебя объект может быть самым обычным, но наружу ты его экспортируешь как константный. Или ни разу не видел, как методы классов возвращают константные ссылки на свои изменчивые атрибуты, репрезентуемые обычными полями? Если такая константа приходит тебе обратно от пользователя, ты имеешь полно право снять с него константность, если вдруг приспичило. Стандарт чётко определяет UB только при модификации исходно константных объектов, для того же поля, которые сразу определены в классе как const, например. Для тех же, на кого константность навешана контрактом интерфейса, но константами в точке определения не являются, никакого UB нет. Надо либо вообще запретить снимать модификатор const и тем самым исключить случаи модификации констант ... |