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

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

switch(mode) {
case ModeA: ... ;
case ModeB: ... ;
case .. /* many of them ... */
default: {
  assert(0 && "Unknown mode!");
  return ADummyValue();
}
};

Теперь я задаюсь вопросом, повлияет ли случай стандартного исправления по умолчанию на скачкообразное преобразование? Представьте, что "ModeA" "ModeB" и т.д. Являются последовательными, поэтому компилятор может оптимизировать таблицу. Поскольку "случай по умолчанию" содержит фактический оператор "return" (поскольку assert исчезнет в режиме деблокирования, и компилятор будет стонать о отсутствующем операторе return), кажется маловероятным, что компилятор оптимизирует ветку по умолчанию.

Какой лучший способ справиться с этим? Некоторый друг рекомендовал мне заменить "ADummyValue" на разыменование нулевого указателя, чтобы компилятор, в присутствии поведения undefined, мог опустить, чтобы предупредить о отсутствующем операторе return. Есть ли лучшие способы решить эту проблему?

Ответ 2

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

if (mode < modeA || mode > modeLast) {
    assert(0 && "Unknown mode!");
    return ADummyValue();
}
switch(mode) { 
    case modeA: ...;
    case modeB: ...;
    case modeC: ...;
    // ...
    case modeLast: ...;
}

Ответ 3

если вы используете "default" (ha!) <assert.h>, определение, привязанное к макросу NDEBUG, возможно, просто

    case nevermind:
#if !defined(NDEBUG)
    default:
        assert("can" && !"happen");
#endif
    }

Ответ 4

Я вижу только 1 решение, если оптимизация фактически нарушена: печально известный "#ifndef NDEBUG" вокруг случая по умолчанию. Не самый приятный трюк, но ясный в этой ситуации.

BTW: вы уже посмотрели, что делает ваш компилятор с и без случая по умолчанию?

Ответ 5

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

И, пожалуйста, не зацикливайтесь на микро оптимизации, если вы на самом деле не измерили (используя профайлер), что они вам нужны.

Ответ 6

Лучший способ справиться с этим - не отключать assert. Таким образом, вы также можете следить за возможными ошибками. Иногда для приложения лучше срабатывать с хорошим сообщением, объясняющим, что именно произошло, а затем продолжить работу.

Ответ 7

Использовать расширения компилятора:

// 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