Есть ли способ использовать постоянный массив с постоянным индексом в качестве метки case-переключателя в C?

У меня есть некоторые постоянные значения и массивы, определяющие их метки и их хэш-коды. Например,

#define LABEL_A 0 //or const int LABEL_A = 0;
#define LABEL_B 1
#define LABEL_C 2
#define LABEL_D 3

const char *VALUE[] = {"LABEL_A", "LABEL_B", "LABEL_C", "LABEL_D"};
const int VALUE_HASH[] = {67490, 67491, 67493, 67459);

Во время выполнения эти метки могут поступать в любом порядке и должны быть соответствующим образом проанализированы. Для этой цели я использую переключатель. Этот код генерирует ошибку во время компиляции "требуется постоянное выражение.

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case VALUE_HASH[LABEL_A]: //line giving compile time error
      break;
    default:
      break;
}

Но, когда я предоставляю фактические константы, он работает. Этот код работает хорошо.

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case 67490: //line not giving compile time error
      break;
    default:
      break;
}
  • Я не могу понять, почему это происходит? Как мой массив, так и его индекс являются константами, то не эквивалентны ли они постоянному литералу?
  • Есть ли другой способ, с помощью которого я могу предоставить свои константы требуемым образом?

Я использую константы таким образом, чтобы обеспечить лучшую семантику кода, читаемость и повторное использование. Не предлагайте решение на основе if-else. В приведенном выше примере есть только 4 метки, но на практике их может быть 100.

Ответ 1

В С++ это компилируется:

#include <stdio.h>
#include <stdlib.h>

constexpr int x[] = { 42, 43 };

int main(int argc, char **argv)
{
    switch(atoi(argv[1]))
    {
        case x[0]: puts("forty_two");
                   break;
        case x[1]: puts("forty_three");
    }
    return 0;
}

Итак, constexpr в массиве представляется решением в современном С++. (Примечание: вопрос был первоначально помечен С++ и C)

Это невозможно в C, если вы хотите сохранить массив. Для корпусов коммутаторов требуется целочисленная константа, но как только вы введете константу целочисленного в переменную, она станет объектом времени выполнения (даже если она объявила const). То, что вы можете сделать, это заменить массив in-memory только кучей прямых определений и, возможно, иметь макрос, который ищет макросы, используя другие макросы (если вы хотите сохранить свою форму кода):

#define LABEL_A 0
#define LABEL_B 1
#define LABEL_C 2
#define LABEL_D 2

#define VALUE_HASH__0 67490
#define VALUE_HASH__2 67491
#define VALUE_HASH__3 67491
#define VALUE_HASH__4 64759

//append what Index expands to to VALUE_HASH__
#define HASH_LOOKUP(Index) MC_cat(VALUE_HASH__,Index) 
#define MC_cat_(X,Y) X##Y
#define MC_cat(X,Y) MC_cat_(X,Y)

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case HASH_LOOKUP(LABEL_A)
      break;
    default:
      break;
}

Ответ 2

Причиной ошибки является просто то, что C не считает, что const int LABEL_A=0; является константой времени компиляции. К сожалению, именно так определяется язык. Его можно решить, используя вместо него #define LABEL_A 0.

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

typedef enum
{
  LABEL_A,
  LABEL_B,
  LABEL_C,
  LABEL_D,
  LABELS_N
} label_index_t;

typedef void func_t (void);

typedef struct
{
  const char* str;
  int         hash;
  func_t*     func;
} value_t;

...

const value_t VALUE [] = 
{
  [LABEL_A] = { .str = "LABEL_A", .hash = 67490, .func = a_func },
  [LABEL_B] = { .str = "LABEL_B", .hash = 67491, .func = b_func },
  [LABEL_C] = { .str = "LABEL_C", .hash = 67493, .func = c_func },
  [LABEL_D] = { .str = "LABEL_D", .hash = 67459, .func = d_func },
};

_Static_assert(sizeof VALUE / sizeof *VALUE == LABELS_N,
               "Size of VALUE does not match label_t.");

...

// instead of switch(n):
VALUE[n].func();

Ответ 3

Для случая переключения требуется, чтобы значения были известны во время компиляции. В вашем случае значений массива значения неизвестны до времени выполнения.

Если вы используете С++ 11, вы можете использовать constexpr, который заставляет компилятор оценивать значения массива во время компиляции. Код ниже отлично работает.

constexpr int VALUE_HASH[] = {67490, 67491, 67493, 67459};

int i = getHashCode(LabelAtRuntime);
switch(i) {
  case VALUE_HASH[LABEL_A]:
    break;
  default:
    break;
}

Ответ 4

Я не знаю, что вы на самом деле делаете. Но когда мне пришлось реализовать пользовательский интерфейс меню в C, я сделал это следующим образом:

// Typedef for a menu item logic function (callback):
typedef void (*menu_item_cb_t)(void*)

// Struct defining a menu item:
typedef struct menu_item {
  const char* label;
  const int hashValue;
  const menu_item_cb_t callback;
  const void* callback_arg;
} menu_item_t;


// Callback for menu item "Do X":
void menu_do_x( void* arg ) {
 // ...
}

// Definition of menu item "Do X":
const menu_item_t menu_item_x = {
  "Do X",
  12345,
  &menu_do_x,
  NULL // Don't need it to do x
}

// All menu items go into one array:
const menu_item_t* MENU[] = { &menu_item_x, ...};
#define MENU_ITEM_CNT xxx

Затем вы можете действовать по выбранному элементу, например:

void menuItemSelected( const char* label ) {
  const int hash = getHashCode(label);
  for ( int i = 0; i < MENU_ITEM_CNT; i++ ) {
    const menu_item_t* const mi = MENU[i];
    if ( hash == mi->hash ) {
      mi->callback( mi->callback_arg );
      break;
    }
  }
}

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

Ответ 5

Если VALUE_HASH используется только для получения констант коммутатора, почему бы не заменить его таблицей переходов?

Обратите внимание: ни одно из приведенных ниже не проверено или даже не составлено. Могут быть синтаксические ошибки.

Сначала определите тип для fucntions в таблице:

typedef void (*Callback)(/* parameters you need */);

Затем вам нужны ваши фактические функции

void labelAProcessing(/* the parameters as per the typedef */)
{
    /// processing for label A
}
// etc

Затем ваша таблица

Callback valueCallbacks[] = { labelAProcessing, labelBProcessing, ... };

И ваш код станет

int i = getHashCode(LabelAtRuntime);
valueCallbacks[i](/* arguments */);

Я не могу слишком подчеркнуть, что i необходимо проверить, чтобы убедиться, что он является допустимым индексом для массива valueCallbacks, так как в противном случае вы можете вызвать случайное число, как если бы оно было функцией.

Ответ 6

Недостаточно, чтобы операнды были константами. Недостаточно знать их во время компиляции (независимо от того, что означает tbat, стандарт C не говорит в этих терминах). Метка case должна быть целочисленным постоянным выражением.

Целочисленные константные выражения строго определяются стандартом C. Грубо говоря, целочисленное константное выражение должно быть построено из целочисленных констант (также перечислений, символьных констант и т.п.) И не может содержать массивы или указатели, даже если они являются постоянными. Для доступного, но полного объяснения см., Например, this.