Comments 46
Не совсем понятно почему упор сделан именно на ошибки с C/C++. Это достаточно распространенный синтаксис и встречается во многих языках.
Ага, сам постоянно туплю, когда одно из условий с отрицанием (когда оба, тогда проще — надо составить условие без отрицаний, а потом || на && и наоборот заменить). Правда == && == у себя в ошибках не встречал.
Видимо потому что PVS-studio чаще всего C\C++ проекты проверяет открытые. А так да, проблема с операторами думаю для всех языков актуальна.
По поводу тернарного оператора — уже давно вошло в привычку брать его в скобки. И условие в свои отдельные скобки. ИМХО — это должно войти в привычку у многих. Иначе от граблей из-за различных приоритетов долго будут страдать все…
И еще — когда идут сложные условия ветвления (больше 1), рекомендуется хоть как-то составить таблицу истинности — никто не застрахован от ошибок, а так за счет пары лишних минут можно прилично уменьшить вероятность создания бага.
И еще — когда идут сложные условия ветвления (больше 1), рекомендуется хоть как-то составить таблицу истинности — никто не застрахован от ошибок, а так за счет пары лишних минут можно прилично уменьшить вероятность создания бага.
if( (pSymDef->GetType() != SbxEMPTY) ||
(pSymDef->GetType() != SbxNULL) )
Наличие скобок в таких примерах не сильно помогает не допустить ошибку. С приоритетом операций в этом примере тоже всё понятно. Я хотел сделать акцент именно на необычные комбинации трёх операторов. Нужно просто запомнить, что такие ситуации бывают.
Жаль, что не было акцента на двух правильных вариантах. Ведь если условное выражение не совпадает с одним из этих вариантов, то значит оно неверно составлено.
Вы правильно сказали, что если условное выражение совпадает с одним из этих вариантов, то, скорее всего, оно составлено неверно.
А вот о правильных вариантах почти невозможно рассуждать, потому что у каждого будет своя логика в программе.
Например, в условии ниже есть ошибка — оно всегда истинно:
Сделав замену оператора || на && мы получим такое условие:
которое будет истинно для любых значений err, не равных code1 и code2. В этом синтетическом примере отсутствует исходная логическая ошибка, но мы не можем утверждать, что это будет правильной правкой в каждом конкретном проекте. А условия, которые не зависят от части логического подвыражения, непонятно даже как исправить на синтетическом примере, не то, что в реальном коде.
А вот о правильных вариантах почти невозможно рассуждать, потому что у каждого будет своя логика в программе.
Например, в условии ниже есть ошибка — оно всегда истинно:
if ( err != code1 || err != code2)
{
....
}
Сделав замену оператора || на && мы получим такое условие:
if ( err != code1 && err != code2)
{
....
}
которое будет истинно для любых значений err, не равных code1 и code2. В этом синтетическом примере отсутствует исходная логическая ошибка, но мы не можем утверждать, что это будет правильной правкой в каждом конкретном проекте. А условия, которые не зависят от части логического подвыражения, непонятно даже как исправить на синтетическом примере, не то, что в реальном коде.
Я несколько о другом говорил. Не знаю, как у всех, но части людей нельзя говорить про то, как делать нельзя, но не говорить про то, как делать можно (хотя оно логически вытекает из запрета). Простой пример: мать посылает сына в магазин за хлебом и говорит: «Купи любой, только батон нарезной не покупай». И пока он собирается и одевается, постоянно напоминает — «не покупай нарезной». Приходит сын в магазин и вспоминает — «Какой хлеб купить надо было? А! Мама постоянно про батон нарезной говорила, куплю его.»
Я не рассуждаю о том, что два оставшихся варианта всегда правильны. Но статья нам доказывает, что перечисленные четыре — всегда избыточны в количестве условий. Поэтому, если наше условное выражение не подходит под != && != или == || ==, то оно заведомо содержит в себе лишние условия.
Кому как, но мне проще запомнить два верных в большинстве случаев выражения, чем запоминать четыре, которые содержат лишнее условие.
Я не рассуждаю о том, что два оставшихся варианта всегда правильны. Но статья нам доказывает, что перечисленные четыре — всегда избыточны в количестве условий. Поэтому, если наше условное выражение не подходит под != && != или == || ==, то оно заведомо содержит в себе лишние условия.
Кому как, но мне проще запомнить два верных в большинстве случаев выражения, чем запоминать четыре, которые содержат лишнее условие.
Не понял пассажа про ошибки и ВУЗ, поясните, пожалуйста.
Эмм, а что это за поток сознания? Где кому какие ошибки в вузе внушают\развивают?
Осталось узнать, как оно работает при таком коде.
Defensive programming. То, что не отловится одной проверкой заметит другая… скорее всего.
С одной стороны даёт возможность создавать современных монстров на сотни миллионов строк кода, а с другой — гарантирует что дыры в безопасности будут «лататься» вечно.
Чтобы дыр не было нужно писать fail-fast/crash-only software и/или использовать GIGO (тогда проверок в системе остаётся мало и каждую из них можно хорошо обдумать), но это замедляет разработку кода, потому так никто не делает…
С одной стороны даёт возможность создавать современных монстров на сотни миллионов строк кода, а с другой — гарантирует что дыры в безопасности будут «лататься» вечно.
Чтобы дыр не было нужно писать fail-fast/crash-only software и/или использовать GIGO (тогда проверок в системе остаётся мало и каждую из них можно хорошо обдумать), но это замедляет разработку кода, потому так никто не делает…
Надо бы ещё добавить проверку флагов. Тоже большой источник ошибок.
«Подстраховать себя от таких ошибок можно путём проверки своего кода с помощью построения таблиц истинности.»
Есть еще один метод — выучить, наконец, законы де Моргана.
Есть еще один метод — выучить, наконец, законы де Моргана.
#include <iostream>
enum ERROR{
code1 = 0,
code2 = 1,
code3 = 2
};
int main(int argc, char const *argv[])
{
ERROR err = code3;
if ( err == code1 || err == code2)
{
std::cout<< "err equal code1 or code2: "<<err<<std::endl;
} else {
std::cout<< "err is: "<<err<<std::endl;
}
return 0;
}
g++ log.cpp
./a.out
err is: 2
Логическое условие выполняется, как и задумано. Т.е. выполняется ветвь кода, когда ERROR err = code3
Это вы к чему написали? В вашем случае ошибки нет.
Т.е. в таких ситуациях писать switch безопаснее. Но длиннее…
Ещё можно было напомнить, что, когда компилятор не вычисляет правую часть выражения, при определённых условиях в || и &&
Ещё можно было напомнить, что, когда компилятор не вычисляет правую часть выражения, при определённых условиях в || и &&
В случае, если заменить условие
if ( err != code1 || err != code2)
Компилятор вычислив левую часть выражения и получив true — правую (после оператора || )вычислять уже не будет, и вывод программы будет ошибочным
if ( err != code1 || err != code2)
Компилятор вычислив левую часть выражения и получив true — правую (после оператора || )вычислять уже не будет, и вывод программы будет ошибочным
#include <iostream>
enum ERROR{
code1 = 0,
code2 = 1,
code3 = 2
};
int main(int argc, char const *argv[])
{
ERROR err = code3;
if ( err != code1 || err != code2)
{
std::cout<< "err equal code1 or code2: "<<err<<std::endl;
} else {
std::cout<< "err is: "<<err<<std::endl;
}
return 0;
}
g++ log.cpp
./a.out
err equal code1 or code2: 2
В предыдущем комментарии забыл поправить иходник.
Тут видя false в первой половине, компилятор вычисляет вторую и она оказывается true — при || итог всего условия true, хотя err == code1
Тут видя false в первой половине, компилятор вычисляет вторую и она оказывается true — при || итог всего условия true, хотя err == code1
#include <iostream>
enum ERROR{
code1 = 0,
code2 = 1,
code3 = 2
};
int main(int argc, char const *argv[])
{
ERROR err = code1;
if ( err != code1 || err != code2)
{
std::cout<< "err isn't equal code1 or code2: "<<err<<std::endl;
} else {
std::cout<< "err is: "<<err<<std::endl;
}
return 0;
}
g++ log.cpp
./a.out
err isn't equal code1 or code2: 0
А еще наверное частая ошибка когда без скобочек используют маски и сравнения вроде if ( a & 3 == b & 3), нет?
А эта проверка только для простых типов проводится, или для всех? А то с C++-ной возможностью перегрузить операторы можно какое угодно поведение получить, и описанные случаи окажутся вполне корректными. Например, если делается интерпретатор/интероп для некоего слаботипизированного языка программирования, где "" == 0 && "" == false – true. Или eDSL с компактным синтаксисом, вроде boost::spirit, где старые операторы наделяют другой семантикой из-за невозможности добавлять новые.
Анализатор ищет неправильные конструкции, какие были приведены в примерах. Но как и во всех диагностиках, для правил V547 и V590 реализован ряд своих исключений, которые позволяют избавить диагностику от ложных срабатываний. Я сейчас не вспомню все исключения, тем более для двух правил. Скорее всего, благодаря им я не встречал ложные срабатывания в описанных вами ситуациях, зато встречал много ошибок на простом коде.
Что также может быть интересно — для пользовательских операторов не работает short-circuit-поведение.
Если речь идет не о элементарных типах, а о классах, оператор сравнения для которых может быть перегружен, то конструкция
if (en_RenderType==RT_BRUSH &&
en_RenderType==RT_FIELDBRUSH)
Может быть не ложной.
if (en_RenderType==RT_BRUSH &&
en_RenderType==RT_FIELDBRUSH)
Может быть не ложной.
Есть много способов выстрелить себе в ногу. Есть целые языки, устроенные так, чтобы люди, их использующие страдали до тех пор, пока не наберутся опыта и не перейдут на что-то менее ужасное. Но… Зачем? Равенство должно быть отношением эквиватентности, незачем из него делать невесть что.
Лучше сразу пристрелите автора, чтоб не мучился и не мучил других.
понятно, что в
if (err == ERR_OK || err != ERR_PENDING)…
можно спокойно убрать кусок с ERR_OK, но КМК иногда это может быть «документацией», что обрабатываем, например, «хороший» и «плохие» сценарии, а «злой», в смысле асинхронный, будет, например, где-то ещё.
Что в общем не должно мешать оторвать руки человеку, придумавшему такой API. 8-)
if (err == ERR_OK || err != ERR_PENDING)…
можно спокойно убрать кусок с ERR_OK, но КМК иногда это может быть «документацией», что обрабатываем, например, «хороший» и «плохие» сценарии, а «злой», в смысле асинхронный, будет, например, где-то ещё.
Что в общем не должно мешать оторвать руки человеку, придумавшему такой API. 8-)
3 и 4 это ошибки. 1 и 2 нет.
!= || !=
== || !=
Оба случая — может достаточно наличия/отсутствия одного из двух признаков. Не согласен совершенно с автором статьи.
!= || !=
== || !=
Оба случая — может достаточно наличия/отсутствия одного из двух признаков. Не согласен совершенно с автором статьи.
Sign up to leave a comment.
Логические выражения в C/C++. Как ошибаются профессионалы