Рассмотрим следующий оператор enum и switch:
typedef enum {
MaskValueUno,
MaskValueDos
} testingMask;
void myFunction(testingMask theMask) {
switch (theMask) {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
default: {} //deal with an unexpected or uninitialized value
}
};
Я программист на Objective-C, но я написал это на чистом C для более широкой аудитории.
Clang / LLVM 4.1 с -Weverything предупреждает меня в строке по умолчанию:
Метка по умолчанию в switch, которая охватывает все значения перечисления
Теперь я могу понять, почему это так: в идеальном мире единственными значениями, входящими в аргумент theMask
, являются перечисления, поэтому по умолчанию не требуется. Но что, если какой-то хак придет и бросит неинициализированный int в мою прекрасную функцию? Моя функция будет предоставлена в виде библиотеки, и я не буду контролировать, что там будет. Использование default
- очень аккуратный способ справиться с этим.
Почему боги LLVM считают такое поведение недостойным своего адского устройства? Должен ли я предшествовать этому оператору if, чтобы проверить аргумент?
"Pro tip: Try setting the -Weverything flag and checking the “Treat Warnings as Errors” box your build settings. This turns on Hard Mode in Xcode."
.-Weverything
может быть полезным, но будьте осторожны с изменением кода, чтобы справиться с ним. Некоторые из этих предупреждений не только бесполезны, но и неэффективны, и их лучше отключать. (Действительно, это тот случай использования-Weverything
: начните с него и выключите то, что не имеет смысла.)Ответы:
Вот версия, которая не страдает ни от сообщений о проблемном кланге, ни от того, от кого вы защищаетесь:
Киллиан уже объяснил, почему clang выдает предупреждение: если вы расширили enum, вы попадете в случай по умолчанию, что, вероятно, не то, что вы хотите. Правильнее всего будет удалить случай по умолчанию и получить предупреждения для необработанных условий.
Теперь вы обеспокоены тем, что кто-то может вызвать вашу функцию со значением, которое находится за пределами перечисления. Это похоже на неспособность выполнить предварительное условие функции: задокументировано ожидание значения из
testingMask
перечисления, но программист пропустил что-то еще. Сделайте так, чтобы ошибка программиста использоваласьassert()
(или,NSCAssert()
как вы сказали, вы используете Objective-C). Выключите вашу программу с сообщением, объясняющим, что программист делает это неправильно, если программист делает это неправильно.источник
return;
и добавлять егоassert(false);
в конец (вместо того, чтобы повторяться, перечисляя юридические перечисления в инициалеassert()
и вswitch
).Наличие
default
метки здесь является показателем того, что вы не понимаете, что вы ожидаете. Поскольку вы исчерпали все возможныеenum
значения явно,default
невозможно выполнить их, и вам также не нужно их защищать от будущих изменений, потому что, если вы расширилиenum
, то конструкция уже сгенерирует предупреждение.Таким образом, компилятор замечает, что вы охватили все основы, но, кажется, думаете, что вы этого не сделали, и это всегда плохой признак. Прилагая минимальные усилия для перехода
switch
к ожидаемой форме, вы демонстрируете компилятору, что то, что вы, похоже, делаете, это то, что вы на самом деле делаете, и вы это знаете.источник
Clang сбит с толку, имея заявление по умолчанию, что вполне приемлемая практика, это называется защитным программированием и считается хорошей практикой программирования (1). Он часто используется в критически важных системах, хотя, возможно, не в настольном программировании.
Целью защитного программирования является обнаружение неожиданных ошибок, которые теоретически никогда бы не произошли. Такая неожиданная ошибка не обязательно означает, что программист вводит в функцию неправильный ввод или даже «злой хак». Скорее всего, это может быть вызвано поврежденной переменной: это может быть связано с переполнением буфера, переполнением стека, кодом разгона и подобными ошибками, не связанными с вашей функцией. А в случае встроенных систем переменные могут измениться из-за EMI, особенно если вы используете внешние схемы RAM.
Что касается того, что пишите внутри оператора по умолчанию ... если вы подозреваете, что программа потеряла свою популярность после того, как вы там оказались, то вам нужна какая-то обработка ошибок. Во многих случаях вы, вероятно, можете просто добавить пустое утверждение с комментарием: «неожиданно, но не важно» и т. Д., Чтобы показать, что вы подумали о маловероятной ситуации.
(1) MISRA-C: 2004 15.3.
источник
-Weverything
включает каждое предупреждение, а не выбор, соответствующий конкретному варианту использования. Несоответствие вашему вкусу - это не то же самое, что путаница.Еще лучше:
Это менее подвержено ошибкам при добавлении элементов в перечисление. Вы можете пропустить тест для> = 0, если вы сделаете свои значения перечисления без знака. Этот метод работает, только если у вас нет пробелов в значениях перечисления, но это часто имеет место.
источник
Тогда вы получите неопределенное поведение , и ваша
default
бессмысленность. Вы ничего не можете сделать, чтобы сделать это лучше.Позвольте мне быть более ясным. В тот момент, когда кто-то передает неинициализированный
int
в вашу функцию, это - неопределенное поведение. Ваша функция может решить проблему остановки, и это не имеет значения. Это UB. После вызова UB вы ничего не сможете сделать.источник
Инструкция по умолчанию не обязательно поможет. Если переключатель находится над перечислением, любое значение, которое не определено в перечислении, в конечном итоге будет выполнять неопределенное поведение.
Насколько вы знаете, компилятор может скомпилировать этот ключ (по умолчанию) следующим образом:
Как только вы запускаете неопределенное поведение, пути назад уже нет.
источник
enum
типа совпадает с диапазоном значений его базового целочисленного типа (который определяется реализацией, следовательно, должен быть самосогласованным).Я также предпочитаю иметь
default:
во всех случаях. Я опаздываю на вечеринку как обычно, но ... некоторые другие мысли, которые я не видел выше:-Werror
) исходит-Wcovered-switch-default
(-Weverything
но не из-Wall
). Если ваша моральная гибкость позволяет вам отключать определенные предупреждения (например, обрезать некоторые вещи с-Wall
или-Weverything
), подумайте о том, чтобы бросать-Wno-covered-switch-default
(или-Wno-error=covered-switch-default
при использовании-Werror
), и вообще-Wno-...
для других предупреждений, которые вы находите неприемлемыми.gcc
(и более общего поведенияclang
), обратитесь кgcc
странице Справочника-Wswitch
,-Wswitch-enum
,-Wswitch-default
для (разные) поведения в подобных ситуациях , перечисленных типов в операторах переключения.Мне также не нравится это предупреждение в концепции, и мне не нравится его формулировка; для меня слова из предупреждения («метка по умолчанию ... охватывает все ... значения») предполагают, что
default:
регистр всегда будет выполняться, напримерПри первом прочтении, я думал, что вы столкнулись с этим - ваше
default:
дело всегда будет исполняться, потому что его нетbreak;
или что-return;
то подобное. Эта концепция похожа (на мой слух) на другие комментарии в стиле няни (хотя иногда и полезные), из которых вытекаетclang
. Еслиfoo == 1
обе функции будут выполнены; ваш код выше имеет такое поведение. Т.е. не удастся прорваться, только если вы захотите продолжить выполнение кода из последующих случаев! Однако, похоже, это не ваша проблема.На риск быть педантичным, некоторые другие мысли для полноты:
int
или что - то в эту функцию, которая явно намеревающаяся потреблять свой собственный тип специфического, ваш компилятор должен защищать вас одинаково хорошо в этой ситуации с агрессивным предупреждением или ошибкой. НО это не так! (То есть, кажется, что по крайней мереgcc
иclang
не делаютenum
проверки типов, но я слышал, что этоicc
делает ). Поскольку вы не получаете безопасность типов , вы можете получить безопасность значений, как описано выше. В противном случае, как предлагается в TFA, рассмотритеstruct
или что-то, что может обеспечить безопасность типов.enum
напримерMaskValueIllegal
, и не поддерживать ееcase
в вашемswitch
. Это будет съеденоdefault:
(в дополнение к любому другому дурацкому значению)Да здравствует защитное кодирование!
источник
Вот альтернативное предложение:
ФП пытается защитить от случая, когда кто-то проходит в том месте,
int
где ожидается перечисление . Или, более вероятно, когда кто-то связал старую библиотеку с более новой программой, используя более новый заголовок с большим количеством случаев.Почему бы не поменять переключатель для обработки
int
дела? Добавление приведенного значения к значению в переключателе устраняет предупреждение и даже дает некоторую подсказку о том, почему существует значение по умолчанию.Я считаю, что это гораздо менее нежелательно, чем
assert()
тестирование каждого из возможных значений или даже предположение, что диапазон значений enum упорядочен, так что работает более простой тест. Это просто уродливый способ делать то, что по умолчанию делает точно и красиво.источник