Как объяснить неопределенное поведение новичкам всезнайки?
Существует несколько ситуаций, которые стандарт C++ приписывает неопределенному поведению. Например, если я выделю с new[]
, а затем попробуйте освободить с delete
(не delete[]
) это неопределенное поведение - все может случиться - это может сработать, может сорваться гадости, может что-то молча испортить и вызвать временную проблему.
Это так проблематично объяснить, что с новичками может случиться что угодно. Они начинают "доказывать", что "это работает" (потому что это действительно работает в реализации C++, которую они используют) и спрашивают "что может быть не так с этим"? Какое краткое объяснение я мог бы дать, чтобы мотивировать их просто не писать такой код?
18 ответов
"Поздравляю, вы определили поведение, которое компилятор выполняет для этой операции. Я ожидаю, что отчет о поведении, который демонстрируют другие 200 существующих в мире компиляторов, будет на моем столе к 10:00 завтра. Не разочаровывайте. мне теперь, ваше будущее выглядит многообещающе!
Неопределенный означает явно ненадежный. Программное обеспечение должно быть надежным. Тебе не нужно больше ничего говорить.
Замерзший пруд - хороший пример неопределенной поверхности для ходьбы. Тот факт, что вы сделали это один раз, не означает, что вы должны добавить ярлык в свой бумажный маршрут, особенно если вы планируете четыре сезона.
Мне на ум приходят две возможности:
Вы можете спросить их: "Просто потому, что в полночь вы можете ехать по автостраде в противоположном направлении и выживать, будете ли вы делать это регулярно?"
Более сложное решение может заключаться в том, чтобы настроить другую среду компилятора / запуска, чтобы показать им, как она эффектно выходит из строя при различных обстоятельствах.
Просто цитата из стандарта. Если они не могут принять это, они не программисты на C++. Будут ли христиане отрицать Библию?;-)
1.9 Выполнение программы
Семантические описания в этом международном стандарте определяют параметризованную недетерминированную абстрактную машину. [...]
Некоторые аспекты и операции абстрактной машины описаны в этом международном стандарте как определяемые реализацией (например,
sizeof(int)
). Они составляют параметры абстрактной машины. Каждая реализация должна включать документацию, описывающую ее характеристики и поведение в этих отношениях. [...]Некоторые другие аспекты и операции абстрактной машины описаны в этом международном стандарте как неопределенные (например, порядок оценки аргументов функции). Там, где это возможно, этот международный стандарт определяет набор допустимого поведения. Они определяют недетерминированные аспекты абстрактной машины. [...]
Некоторые другие операции описаны в этом международном стандарте как неопределенные (например, эффект разыменования нулевого указателя). [Примечание: этот международный стандарт не устанавливает требований к поведению программ, которые содержат неопределенное поведение. —Конечная записка]
Вы не можете получить ничего более ясного, чем это.
Я бы объяснил, что если бы они не написали код правильно, их следующий обзор производительности не был бы счастливым. Этого достаточно "мотивации" для большинства людей.
Превратите человека в указатель. Скажите им, что они являются указателем на класс человек, и вы вызываете функцию "RemoveCoat". Когда они указывают на человека и говорят "RemoveCoat", все в порядке. Если у человека нет пальто, не стоит беспокоиться - мы проверяем это, все, что действительно делает RemoveCoat, это снимает верхний слой одежды (с проверкой на порядочность).
Теперь, что произойдет, если они будут указывать куда-то случайно и они скажут RemoveCoat - если они указывают на стену, краска может отслоиться, если они указывают на дерево, на котором может оторваться кора, собаки могут побриться, USS Enterprise может опустить щиты в критический момент и т. д.!
Нет способа выяснить, что может случиться, поведение не было определено для этой ситуации - это называется неопределенным поведением и его следует избегать.
Мне нравится эта цитата:
Неопределенное поведение: оно может повредить ваши файлы, отформатировать диск или отправить ненавистное письмо вашему боссу.
Я не знаю, кому это можно приписать (может, это из Effective C++)?
Пусть они пробуют свой путь, пока их код не рухнет во время теста. Тогда слова не понадобятся.
Дело в том, что у новичков (мы все были там) есть некоторое количество эго и уверенности в себе. Это нормально. На самом деле, вы не могли бы быть программистом, если бы не сделали. Важно обучать их, но не менее важно поддерживать их и не сокращать их начало в путешествии, подрывая их доверие к себе. Просто будьте вежливы, но доказывайте свою позицию фактами, а не словами. Только факты и доказательства будут работать.
Спокойно переопределите новый, новый [], удалите и удалите [] и посмотрите, сколько времени он заметит;)
В противном случае... просто скажите ему, что он неправ, и укажите ему на спецификацию C++. Ах да.. и в следующий раз будьте более осторожны, когда нанимаете людей, чтобы быть уверенными, что вы избегаете дыр!
Короче говоря, вы не можете использовать sizeof() для структуры, элементы которой не определены, и если вы это сделаете, демоны могут вылететь из вашего носа.
"Демоны могут вылететь из твоего носа" просто должны быть частью словаря каждого программиста.
Более того, поговорим о переносимости. Объясните, как часто программы должны переноситься на разные операционные системы, не говоря уже о разных компиляторах. В реальном мире порты обычно создаются не программистами, а другими людьми. Некоторые из этих портов предназначены даже для встраиваемых устройств, где обнаружение того, что компилятор решил не так, как вы предполагали, может стоить огромных затрат.
C++ на самом деле не является языком для дилетантов, и просто перечисление некоторых правил и их соблюдение без вопросов сделает для некоторых ужасных программистов; большинство глупостей, которые, как я вижу, говорят люди, вероятно, связаны с такими слепыми правилами, которые следуют за / законами.
С другой стороны, если они знают, что деструкторы не будут вызваны, и, возможно, возникнут другие проблемы, они позаботятся об этом. И что еще более важно, имейте некоторый шанс отладить его, если они когда-либо сделают это случайно, и также иметь некоторый шанс понять, насколько опасными могут быть многие функции C++.
Поскольку есть много вещей, о которых нужно беспокоиться, ни один курс или книга никогда не заставит кого-то освоить C++ или, возможно, даже стать настолько хорошим с ним.
Один будет...
"Это" использование не является частью языка. Если бы мы сказали, что в этом случае компилятор должен генерировать код, который дает сбой, то это будет особенность, своего рода требование для производителя компилятора. Авторы стандарта не хотели давать ненужную работу над "функциями", которые не поддерживаются. Они решили не предъявлять никаких поведенческих требований в таких случаях.
Одна вещь, еще не упомянутая в отношении неопределенного поведения, заключается в том, что если выполнение какой-либо операции приведет к неопределенному поведению, соответствующая стандартам реализация может на законных основаниях, возможно, попытаться быть "полезной" или повысить эффективность, сгенерировать код, который потерпит неудачу, если такая операция были предприняты попытки Например, можно представить многопроцессорную архитектуру, в которой любая ячейка памяти может быть заблокирована, и попытка получить доступ к заблокированной ячейке (кроме как разблокировать ее) будет зависать до тех пор, пока эта ячейка не будет разблокирована. Если блокировка и разблокировка были очень дешевыми (правдоподобно, если они реализованы аппаратно), такая архитектура могла бы быть полезной в некоторых многопоточных сценариях, поскольку реализация x++
as (атомарное чтение и блокировка x; добавление единицы для чтения значения; атомарное разблокирование и запись x) гарантируют, что если оба потока выполняются x++
одновременно результатом будет добавление двух к x. При условии, что программы написаны так, чтобы избежать неопределенного поведения, такая архитектура может упростить разработку надежного многопоточного кода, не требуя больших неуклюжих барьеров памяти. К сожалению, заявление как *x++ = *y++;
может вызвать тупик, если x
а также y
были обе ссылки на одно и то же место хранения, и компилятор попытался передать код как t1 = read-and-lock x; t2 = read-and-lock y; read t3=*t1; write *t2=t3; t1++; t2++; unlock-and-write x=t1; write-and-unlock y=t2;
, Хотя компилятор может избежать тупиковой ситуации, воздерживаясь от чередования различных операций, это может снизить эффективность.
Скомпилируйте и запустите эту программу:
#include <iostream>
class A {
public:
A() { std::cout << "hi" << std::endl; }
~A() { std::cout << "bye" << std::endl; }
};
int main() {
A* a1 = new A[10];
delete a1;
A* a2 = new A[10];
delete[] a2;
}
По крайней мере, при использовании GCC, это показывает, что деструктор вызывается только для одного из элементов при выполнении одного удаления.
О единственном удалении на массивах POD. Направьте их на C++ FAQ или попросите их выполнить свой код через cppcheck.
То, что их программа работает, - гарантия ничто; компилятор может генерировать код, который работает (как вы вообще определяете "работу", когда правильное поведение не определено?) в будние дни, но форматирует ваш диск в выходные. Читали ли они исходный код своего компилятора? Изучите их разобранный вывод?
Или напомните им только то, что такое "работает" сегодня не гарантирует его работоспособность при обновлении версии компилятора. Скажите им, чтобы они веселились, обнаруживая, что из этого вылезают незаметные ошибки.
И действительно, почему бы и нет? Они должны предоставить обоснованный аргумент для использования неопределенного поведения, а не наоборот. Какова причина, чтобы использовать delete
вместо delete[]
кроме лени? (Хорошо, есть std::auto_ptr
, Но если вы используете std::auto_ptr
с new[]
распределенный массив, вы, вероятно, должны использовать std::vector
тем не мение.)
В стандартах C и C++ термин "неопределенное поведение" используется для обозначения ситуаций, в которых для разных реализаций может быть полезно обрабатывать конструкции в различных несовместимых моделях, некоторые из которых будут вести себя предсказуемо, а некоторые - нет. Оба используют одну и ту же терминологию для описания UB, и, хотя я не знаю ни одного опубликованного Обоснования стандартов C++, в Обосновании стандарта C говорится:
Неопределенное поведение дает разработчику лицензию не обнаруживать определенные программные ошибки, которые трудно диагностировать. Он также определяет области возможного соответствующего расширения языка: разработчик может дополнить язык, предоставив определение официально неопределенного поведения ".
Обратите внимание, что многие действия, которые были классифицированы как неопределенное поведение стандартом C, считались полностью определенными во многих, если не во всех реализациях, но авторы стандарта хотели дать разработчикам, ориентированным на необычные платформы или поля приложений, возможность отклоняться от нормального поведения, если это принесет пользу их клиентам. Такая свобода не предназначалась для произвольных и капризных отклонений от прецедента, которые затрудняли бы программистам возможность быстро и легко сделать то, что необходимо.
К сожалению, многие программисты, использующие gcc и clang, не понимают их потребностей так же, как разработчики этих компиляторов, которые понимают, что, поскольку стандарт избегает обязательного требования чего-либо, что могло бы снизить эффективность приложений, которые никогда не будут получать злонамеренно созданные входы или будет работать только в контекстах, где даже вредоносные программы не смогут ничего повредить, это означает, что нет необходимости в каких-либо реализациях, позволяющих программистам легко и эффективно писать программы, подходящие для использования в других контекстах.
Расскажите им о стандартах и о том, как разработаны инструменты для соответствия стандартам. Все, что находится за пределами стандарта, может или не может работать, что является UB.
Включите malloc_debug и delete
массив объектов с деструкторами. free
Указатель внутри блока должен потерпеть неудачу. Соберите их всех вместе и продемонстрируйте это.
Вам нужно будет подумать о других примерах, чтобы укрепить свой авторитет, пока они не поймут, что они новички, и есть много информации о C++.