В C и C ++, какие методы могут предотвратить случайное использование присваивания (=), где требуется эквивалентность (==)?

15

В C и C ++ очень легко написать следующий код с серьезной ошибкой.

char responseChar = getchar();
int confirmExit = 'y' == tolower(responseChar);
if (confirmExit = 1)
{
    exit(0);
}

Ошибка в том, что оператор if должен был быть:

if (confirmExit == 1)

Как закодировано, он будет выходить каждый раз, потому что происходит присвоение confirmExitпеременной, а затем confirmExitиспользуется как результат выражения.

Есть ли хорошие способы предотвратить такую ​​ошибку?

DeveloperDon
источник
39
Да. Включите предупреждения компилятора. Относитесь к предупреждениям как к ошибкам, и это не проблема.
Мартин Йорк
9
В приведенном примере решение простое. Вы присвоить ему логическое значение, таким образом , использовать его как логическое значение: if (confirmExit).
Безопасное
4
Проблема заключается в том, что ошибка была допущена «дизайнерами» языка Си, когда они решили использовать = для оператора присваивания и == для сравнения на равенство. ALGOL использовал: =, потому что они специально хотели использовать = для сравнения на равенство, а PASCAL и Ada следовали решению ALGOL. (Стоит отметить, что, когда DoD потребовал вступления на основе C в отбор DoD1, что в конечном итоге привело к Ada, Bell Labs отказалась, заявив, что «C не был сейчас и никогда не будет достаточно надежным для критически важного для DoD». программное обеспечение. "Хотелось бы, чтобы DoD и подрядчики слушали Bell Labs по этому вопросу.)
Джон Р. Штром
4
@ Джон, выбор символов не проблема, это тот факт, что присваивания также являются выражением, которое возвращает присвоенное значение, допуская либо одно, a = bлибо a == bусловное выражение .
Карл Билефельдт

Ответы:

60

Лучший способ - повысить уровень предупреждения вашего компилятора. Затем он предупредит вас о возможном назначении в условном условии.

Убедитесь, что вы компилируете свой код с нулевыми предупреждениями (что вы должны делать в любом случае). Если вы хотите быть педантичным, тогда настройте свой компилятор так, чтобы предупреждения воспринимались как ошибки.

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

Предупреждения являются действительно логическими ошибками в коде и должны быть исправлены.

Мартин Йорк
источник
2
Обязательно используйте предупреждения, а не условности Йоды - сначала вы можете забыть сделать условную константу так же легко, как забыть сделать ==.
Майкл Кохн
8
Я получил приятный сюрприз, что самым популярным ответом на этот вопрос является «превратить предупреждения компилятора в ошибки». Я ожидал if (0 == ret)ужаса.
Джеймс
2
@James: ... не говоря уже о том, что это не сработает a == b!!
Эмилио Гаравалья
6
@EmilioGaravaglia: Для этого есть простое решение: просто напишите 0==a && 0==b || 1==a && 1==b || 2==a && 2==b || ...(повторите для всех возможных значений). Не забывайте об обязательной ...|| 22==a && 22==b || 23==a && 24==b || 25==a && 25==b ||... ошибке, иначе программистам не понравится.
user281377
10

Вы всегда можете сделать что-то радикальное, например, протестировать свое программное обеспечение Я даже не имею в виду автоматические модульные тесты, просто тесты, которые каждый опытный разработчик делает по привычке: дважды запускает свой новый код, один раз подтверждая выход, а второй - нет. Вот почему большинство программистов считают это не проблемой.

Карл Билефельдт
источник
1
Это легко сделать, когда поведение вашей программы полностью детерминировано.
Джеймс
3
Эта конкретная проблема может быть трудно проверить. Я видел людей, укушенных rc=MethodThatRarelyFails(); if(rc = SUCCESS){более одного раза, особенно если метод дает сбой только в условиях, которые трудно проверить.
Gort the Robot
3
@StevenBurnap: Вот для чего предназначены фиктивные объекты. Правильное тестирование включает в себя тестирование режимов отказа.
Ян Худек
Это правильный ответ.
Легкость гонок с Моникой
6

Традиционный способ предотвратить неправильное использование присваиваний в выражении - поместить константу слева и переменную справа.

if (confirmExit = 1)  // Unsafe

if (1=confirmExit)    // Safe and detected at compile time.

Компилятор сообщит об ошибке при неправильном присвоении константы, аналогичной следующей.

.\confirmExit\main.cpp:15: error: C2106: '=' : left operand must be l-value

Пересмотрено, если условие будет:

  if (1==confirmExit)    

Как показано в комментариях ниже, многие считают это неуместным методом.

DeveloperDon
источник
8
Также известный как условные выражения Йоды: wiert.me/2010/05/25/… и stackoverflow.com/questions/8591182/… и programmers.stackexchange.com/questions/16908/…
Мартин Йорк
13
Следует отметить, что такие действия сделают вас довольно непопулярными.
Rwalk
11
Пожалуйста, не рекомендуйте эту практику.
Джеймс
5
Это также не работает, если вам нужно сравнить две переменные друг с другом.
Ден04
Некоторые языки фактически позволяют присваивать 1 новому значению (да, я знаю, что Q - это бой c / c ++)
Матсеманн
4

Я согласен со всеми, кто говорит «предупреждения компилятора», но я хочу добавить еще одну технику: обзоры кода. Если у вас есть политика проверки всего кода, который был зафиксирован, предпочтительно до того, как он будет зафиксирован, то, скорее всего, такого рода вещи будут обнаружены во время проверки.

MatrixFrog
источник
Я не согласен. Особенно = вместо == можно легко проскользнуть, просмотрев код.
Симон
2

Во-первых, повышение уровня предупреждения никогда не повредит.

Если вы не хотите, чтобы ваше условное выражение проверяло результат присваивания внутри самого оператора if, а затем много лет работал со многими программистами на C и C ++ и никогда не слышал, что сначала сравнивать константу if(1 == val)было плохо, вы мог бы попробовать эту конструкцию.

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

Однако, если вы намеревались проверить результат присваивания, то использование более высоких предупреждений могло бы [вероятно, перехватить] присвоение константе.

octopusgrabbus
источник
Я скептически поднимаю бровь на любого начинающего программиста, который видит условие Йоды и не сразу понимает его. Это просто щелчок, не сложный для чтения и, конечно, не такой плохой, как утверждают некоторые комментарии.
underscore_d
@underscore_d У большинства моих работодателей были отклонены назначения в рамках условного соглашения. Мысль заключалась в том, что было лучше отделить задание от условного. Причиной для большей ясности, даже за счет другой строки кода был поддерживающий инженерный фактор. Я работал в местах с большими базами кода и большим количеством работ по обслуживанию. Я полностью понимаю, что кто-то может захотеть присвоить значение и перейти к условию, полученному в результате присвоения. Я вижу, что это делается чаще всего в Perl, и, если цель ясна, я буду следовать шаблону дизайна автора.
octopusgrabbus
Я думал только об условных выражениях Йоды (например, о вашей демонстрации), а не о назначении (как в ОП). Я не возражаю против первого, но последнее тоже не очень нравится. Единственная форма, которую я намеренно использую, заключается if ( auto myPtr = dynamic_cast<some_ptr>(testPtr) ) {в том, что она позволяет избежать бесполезного nullptrохвата в случае сбоя приведения - что, вероятно, является причиной того, что C ++ имеет такую ​​ограниченную способность назначать в условных выражениях. В остальном, да, определение должно иметь свою собственную линию, я бы сказал - намного легче увидеть с первого взгляда и менее склонно к разным промахам.
underscore_d
@underscore_d Отредактированный ответ на основе ваших комментариев. Хорошая точка зрения.
octopusgrabbus
1

Опоздал на вечеринку, как всегда, но ключом здесь является статический анализ кода.

Большинство IDE теперь предоставляют SCA сверх синтаксической проверки компилятора, и доступны другие инструменты, в том числе те, которые реализуют рекомендации MISRA (*) и / или CERT-C.

Заявление: я являюсь частью рабочей группы MISRA C, но я публикую пост в личном качестве. Я также независим от любого поставщика инструмента

Андрей
источник
-2

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

Перевернутая лама
источник
4
Вы будете удивлены тем, как мало бессмысленных предупреждений генерируют современные компиляторы. Возможно, вы не считаете предупреждение важным, но в большинстве случаев вы просто ошибаетесь.
Кристоф Провост
Ознакомьтесь с книгой "Написание твердого кода" amazon.com/Writing-Solid-Microsoft-Programming-Series/dp/… . Он начинается с большой дискуссии о компиляторах и о том, какую пользу мы можем получить от предупреждающих сообщений и даже более обширного статического анализа.
DeveloperDon
@KristofProvost Мне удалось заставить Visual Studio выдать мне 10 копий одного и того же предупреждения с той же строкой кода, а затем, когда эта проблема была «исправлена», это привело к 10 одинаковым предупреждениям об одной и той же строке кода о том, что оригинальный метод был лучше.
Инвертированная лама