Ограничить путаницу, вызванную неопределенным поведением? - PullRequest
5 голосов
/ 12 января 2010

Как я понял из моего чтения, undefined-поведение является результатом того, что компилятор оставил несколько неидентичных альтернатив во время компиляции. Однако не означает ли это, что если следовать строгой практике кодирования (например, помещать каждое назначение и каждое равенство в отдельном утверждении, правильную отладку и комментирование), то это не должно создавать значительных проблем при поиске источника неопределенного -behavior.

Кроме того, для каждой ошибки, которая возникает, если вы идентифицируете код, вы должны знать, какие операторы могут использоваться вместо этого конкретного оператора, верно?

РЕДАКТИРОВАТЬ: Меня не интересуют места, где вы написали код, который вы не хотели писать. Меня интересуют примеры, когда код, который звучит по математической логике, не работает.

Кроме того, я считаю «хорошей практикой кодирования» сильные информативные комментарии через каждые несколько строк, правильные отступы и регулярные отладочные дампы.

Ответы [ 4 ]

10 голосов
/ 12 января 2010

Неопределенное поведение не обязательно оставляет компилятору несколько вариантов. Чаще всего это просто делать что-то, что не имеет смысла.

Например, возьмите этот код:

int arr[2];
arr[200] = 42;

это неопределенное поведение. Дело не в том, что компилятору было предоставлено несколько вариантов выбора. просто то, что я делаю, не имеет смысла. В идеале, это не должно быть разрешено в первую очередь, но без потенциально дорогой проверки во время выполнения мы не можем гарантировать, что нечто подобное не произойдет в нашем коде. Таким образом, в C ++ правило состоит в том, что язык определяет только поведение программы, которая придерживается правил. Если он делает что-то ошибочное, как в приведенном выше примере, это просто undefined , что должно произойти.

Теперь представьте, как вы собираетесь обнаружить эту ошибку. Как это выходит на поверхность? Это может никогда , кажется, вызвать какие-либо проблемы. Возможно, мы просто так записываем в память, которая сопоставлена ​​с процессом (поэтому мы не получаем нарушения доступа), но никогда не используем иначе (поэтому никакая другая часть программы не будет читать наше значение мусора или перезаписывать то, что мы написали ), Тогда может показаться, что программа не содержит ошибок и работает просто отлично.

Или он может попасть на адрес, который даже не отображается в нашем процессе. Тогда программа сразу вылетит.

Или он может попасть на адрес, который сопоставлен с нашим процессом, но в какой-то момент позже будет использоваться для чего-то. Тогда все, что мы знаем, это то, что рано или поздно функция, читающая с этого адреса, получит неожиданное значение и будет вести себя странно. Эту часть легко обнаружить в отладчике, но она ничего не говорит нам о , когда или из , где , это значение мусора было записано. Поэтому нет простого способа отследить ошибку до ее источника.

7 голосов
/ 12 января 2010

Во-первых, некоторые определения из стандарта C ++ 03:

1.3.5 поведение, определяемое реализацией

Поведение, для правильно сформированной программы, построения и правильных данных, которое зависит от реализации и что каждая реализация должна документировать

1.3.12 неопределенное поведение

Поведение, которое может возникнуть при использовании ошибочной программной конструкции или ошибочных данных, к которым настоящий международный стандарт не предъявляет никаких требований. Неопределенное поведение также может ожидаться, когда в этом международном стандарте опущено описание любого явного определения или поведения.

1.3.13 неуточненное поведение

Поведение для правильно сформированной программы, которая строит и корректирует данные, которые зависят от реализации. Реализация не обязана документировать, какое поведение происходит.

Несмотря на то, что неопределенное поведение можно назвать UB, я никогда не видел этого, и UB всегда означает неопределенное поведение. Во всем стандарте есть утверждения, похожие на "выполнение X неопределено поведение ", но иногда вы сталкиваетесь с делом, которое просто не охвачено.

Другими словами, если у вас есть какое-либо неопределенное поведение, тогда все ставки выключены . Что касается стандарта, ваша программа может делать все, что угодно, от приглашения свекрови на выходные SuperBowl до nethack . Из-за природы UB вы не можете проверить это, и вы не можете ожидать никакой помощи от компилятора. (Хотя для некоторых тривиальных распространенных ошибок компиляторы обычно производят диагностику.)

Обычно что-то определяется как UB, потому что это логически не имеет смысла (например, доступ к массиву вне границ), но также часто, потому что это потребует, чтобы реализация выполняла слишком много работы, чтобы предотвратить - часто во время выполнения. Помните, что C ++ является производным от C, и способность создавать высокооптимизированные программы является основной целью обоих языков. С этой целью языки обращаются к программисту, чтобы убедиться в правильности кода в этих ситуациях, что связано с принципом «вы не платите за то, что не используете».

Итак, наконец, UB - это плохо, очень плохо; избежать этого любой ценой. Однако трудная часть UB не знает, что это такое и при каких обстоятельствах это происходит; сложная часть распознавания, когда вы вызываете UB. Например:

std::string s = "abc";
char& c = s[0];
cout.write(s.data(), s.length());
c = '-';

Выглядит вполне разумно, верно? Нет, это UB, но будет работать так, как вы ожидаете, во всех популярных реализациях.

1 голос
/ 12 января 2010

Как я понял из моего чтения, undefined-поведение является результатом того, что компилятор оставил несколько неидентичных альтернатив во время компиляции.

Хотя это может быть один источник неопределенного поведения, вы говорите слишком абстрактно. Вам нужен конкретный пример того, что вы подразумеваете под «неидентичными альтернативами во время компиляции».

Если под «следовать строгой практике кодирования» вы подразумеваете не использовать логику, которая приводит к неопределенному поведению, тогда да (потому что не было бы неопределенного поведения). Отслеживание ошибки из-за неопределенного поведения может или не может быть проще, чем отслеживание ошибки, вызванной логической ошибкой.

Обратите внимание, что код, который приводит к "неопределенному поведению", все еще является допустимым кодом C ++. Я считаю это классом кода / логики, который следует использовать очень редко, когда это «неопределенное поведение» предсказуемо для данной программы на данной платформе с данной реализацией. Вы найдете случаи, когда то, что язык считает «неопределенным поведением», будет фактически определено для конкретной среды / набора ограничений.

1 голос
/ 12 января 2010

Я не уверен, есть ли формальное определение «неопределенного поведения», но следование хорошим стандартам кодирования может уменьшить двусмысленность и привести к меньшему количеству дефектов компиляции и времени выполнения.

Тем не менее, заставить двух программистов договориться о том, что такое «хорошие стандарты кодирования», является сложным и подверженным ошибкам процессом.

На ваш второй вопрос: да, компиляторы, как правило, выводят код ошибки, который вы можете использовать для решения проблемы

...