Случай переключателя "по умолчанию" мешает оптимизации таблицы переходов?
В моем коде я использовал для написания резервных случаев по умолчанию, содержащих утверждения, подобные следующим, чтобы защитить меня от забвения обновления ключа в случае изменения семантики
switch(mode) {
case ModeA: ... ;
case ModeB: ... ;
case .. /* many of them ... */
default: {
assert(0 && "Unknown mode!");
return ADummyValue();
}
};
Теперь мне интересно, будет ли случай по умолчанию с искусственной резервной проверкой мешать генерации таблицы переходов? Представьте, что "ModeA" и "ModeB" и т. Д. Являются последовательными, чтобы компилятор мог оптимизироваться в таблицу. Поскольку в случае "по умолчанию" содержится фактический оператор "возврата" (так как утверждение исчезнет в режиме выпуска, а компилятор будет жаловаться на отсутствующий оператор возврата), маловероятно, что компилятор оптимизирует ветку по умолчанию.
Какой лучший способ справиться с этим? Кто-то из моих друзей порекомендовал мне заменить "ADummyValue" на разыменование нулевого указателя, чтобы компилятор при наличии неопределенного поведения мог пропустить предупреждение о пропущенном операторе возврата. Есть ли лучшие способы решить эту проблему?
7 ответов
Если ваш компилятор MSVC, вы можете использовать __assume
встроенный: http://msdn.microsoft.com/en-us/library/1b3fsfxw(v=VS.80).aspx
По крайней мере, с компиляторами, на которые я смотрел, ответ, как правило, нет. Большинство из них скомпилирует оператор switch следующим образом:
if (mode < modeA || mode > modeLast) {
assert(0 && "Unknown mode!");
return ADummyValue();
}
switch(mode) {
case modeA: ...;
case modeB: ...;
case modeC: ...;
// ...
case modeLast: ...;
}
Если вы используете "по умолчанию" (ха!) <assert.h>
определение в любом случае связано с макросом NDEBUG, так что, возможно, просто
case nevermind:
#if !defined(NDEBUG)
default:
assert("can" && !"happen");
#endif
}
Лучший способ справиться с этим - не отключать assert. Таким образом, вы также можете следить за возможными ошибками. Иногда лучше завершить работу приложения с хорошим сообщением, объясняющим, что именно произошло, а затем продолжить работу.
Если у вас есть состояние, которое никогда не должно быть достигнуто, вы должны убить программу, потому что она только что достигла неожиданного состояния, даже в режиме выпуска (вы можете просто быть более дипломатичным и реально сохранять данные пользователей и делать все эти другие приятные вещи прежде чем спуститься).
И, пожалуйста, не зацикливайтесь на микрооптимизациях, если только вы не измерили (используя профилировщик), что они вам нужны.
Я вижу только 1 решение в случае, если оптимизация действительно нарушена: печально известный "#ifndef NDEBUG" вокруг случая по умолчанию. Не самый лучший трюк, но понятный в этой ситуации.
Кстати, вы уже посмотрели, что делает ваш компилятор с делом по умолчанию и без него?
Используйте расширения компилятора:
// assume.hpp
#pragma once
#if defined _MSC_VER
#define MY_ASSUME(e) (__assume(e), (e) ? void() : void())
#elif defined __GNUC__
#define MY_ASSUME(e) ((e) ? void() : __builtin_unreachable())
#else // defined __GNUC__
#error unknown compiler
#endif // defined __GNUC__
-
// assert.hpp
#include <cassert>
#include "assume.hpp"
#undef MY_ASSERT
#ifdef NDEBUG
#define MY_ASSERT MY_ASSUME
#else // NDEBUG
#define MY_ASSERT assert
#endif // NDEBUG