Случай переключателя "по умолчанию" мешает оптимизации таблицы переходов?

В моем коде я использовал для написания резервных случаев по умолчанию, содержащих утверждения, подобные следующим, чтобы защитить меня от забвения обновления ключа в случае изменения семантики

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
Другие вопросы по тегам