Есть ли способ использовать постоянный массив с постоянным индексом в качестве метки регистра переключателя в 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.
6 ответов
В C++ это компилируется:
#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++ и C)
Это невозможно в C, если вы хотите сохранить массив. В случаях переключения требуется целочисленная константа, но как только вы поместите целочисленную константу в переменную, она станет сущностью времени выполнения (даже если она объявлена как const). Что вы могли бы сделать, это заменить массив в памяти просто набором прямых определений и, возможно, иметь макрос, который ищет макросы, используя другие макросы (если вы хотите сохранить свою форму кода):
#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;
}
Причина ошибки в том, что 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();
Случай переключения требует, чтобы значения были известны во время компиляции. В вашем случае значений массива, значения не известны до времени выполнения.
Если вы используете C++11, вы можете использовать constexpr
что заставляет компилятор оценивать значения массива во время компиляции. Код ниже работает нормально.
constexpr int VALUE_HASH[] = {67490, 67491, 67493, 67459};
int i = getHashCode(LabelAtRuntime);
switch(i) {
case VALUE_HASH[LABEL_A]:
break;
default:
break;
}
Я не знаю, что ты на самом деле задумал. Но когда мне пришлось реализовать пользовательский интерфейс меню на C, я сделал это так:
// Typedef for a menu item's 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;
}
}
}
Этот подход, конечно, может быть различным, но я надеюсь, что вы поняли идею. Это в основном только для определения элементов с определенными свойствами ("метка", "хэш" и т. Д.) И непосредственное связывание их с функцией, которая реализует соответствующее действие для этого элемента.
Для операндов недостаточно быть константами. Недостаточно, чтобы их знали во время компиляции (что бы ни значило tbat, стандарт C не говорит в этих терминах). Метка регистра должна быть целочисленным константным выражением.
Целочисленные константные выражения строго определены стандартом Си. Грубо говоря, целочисленное константное выражение должно быть построено из целочисленных констант (также перечислителей, символьных констант и т. П.) И не может содержать массивы или указатели, даже если они постоянны. Для доступного, но подробного объяснения см., Например, это.
Если VALUE_HASH
используется только для получения констант переключателя, почему бы не заменить его таблицей переходов?
Обратите внимание, что ничего из нижеперечисленного не тестируется и даже не компилируется Там могут быть синтаксические ошибки.
Сначала определите тип для функций в таблице:
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
массив, так как в противном случае вы можете вызвать случайное число, как если бы это была функция.