Могу ли я использовать препроцессор, чтобы сделать это более понятным?

Я писал небольшую функцию исходного файла для моего Pic32 и застрял на одном. По сути, это утилита, которая должна сохранять входящие char данные в буфер, а затем, если получены '\r', она сравнивает буфер со списком команд (в массиве names), и если совпадение найдено, возвращается индекс элемента.

Эта часть из заголовка:

#define NAMECNT 6    
static const char names[NAMESCNT][10] = {   // 6commands, max 10 char each
        "korr",         // 1
        "adc",          // 2
        "fft",          // 3
        "data",         // 4 
        "pr",           // 5
        "prsc"};        // 6

/* functions */
extern int comm(char cdata);

В основном файле есть один большой переключатель:

switch( comm(recieved_ch) ){
case 1: foo1(); break; 
case 2: foo2(); break;
...
}

Теперь, для большей ясности, я хотел использовать вместо 1, 2, ... исходные имена (например, case KORR: case ADC:), поэтому я написал определения для каждого из них.

#define KORR 1
#define ADC 2

Но мне не нравится это решение, потому что я хочу использовать этот исходный файл в большем количестве проектов, и для каждого из них будет свой список команд. Есть ли способ, как это сделать? Лучше всего было бы создать имена массивов в препроцессоре, но я сомневаюсь, что это вообще возможно. Я думал об использовании типа enum (который будет иметь те же элементы, что и список команд names), но я не уверен, как это будет происходить.


person L. Zeda    schedule 05.09.2016    source источник
comment
Если вы создаете пары массив/перечисление, эта статья может оказаться полезной.   -  person user694733    schedule 05.09.2016
comment
Массив static const, определенный в заголовке? Пожалуйста, переместите это в исходный файл и замените extern декларацией, пока кто-нибудь не потерял глаз. Сомневаюсь, что всем вашим клиентам нужны собственные копии констант.   -  person sendaran    schedule 05.09.2016
comment
@sendaran Это для моего собственного использования (исследования HW только для исследований и разработок), и этот массив находится в заголовке по назначению, поскольку он действует как список команд, которые можно и нужно изменять ...   -  person L. Zeda    schedule 05.09.2016


Ответы (2)


Вы можете использовать X-макросы для создания enum и заполнения массива, затем вы можете использовать enum значения в switch:

#define VARS \
    X(korr) \
    X(adc)  \
    X(fft)  \
    X(data) \
    X(pr)   \
    X(prsc)

static const char names[][10] = {   // 6commands, max 10 char each
#define X(name) #name,
    VARS
#undef X
};

enum evars {
#define X(name) name, 
    VARS
#undef X
};

extern int comm(char cdata);

int main(void)
{
    char x = 1;

    switch (comm(x)) {
        case korr:
            printf("korr");
            break;
        case adc:
            printf("adc");
            break;
        /* ... and so on */
    }
    return 0;
}

Расширение X:

static const char names[][10] = {

 "korr", "adc", "fft", "data", "pr", "prsc",

};

enum evars {

 korr, adc, fft, data, pr, prsc,

};

Изменить: как указал @ 5gon12eder, вам не нужно жестко кодировать 6 в первом измерении массива (вы можете не указывать его).

person David Ranieri    schedule 05.09.2016
comment
Пожалуй, это лучшее решение в данном случае. Единственное улучшение, которое я вижу, заключается в том, что NAMESCNT не нужно жестко кодировать до 6, но его можно определить из массива, который может быть объявлен с неопределенным первым измерением. X-макрос может (если функции именуются систематически) также использоваться для создания массива указателей на функции, как это предлагается в другом ответе. В этом случае вам действительно понадобится оператор вставки токена. - person 5gon12eder; 05.09.2016
comment
Вот так и сделаю, большое спасибо. NAMESCNT жестко закодирован, потому что он использовался снаружи, в основном исходном файле, и изначально у меня был этот список names как статический в файле .c. - person L. Zeda; 05.09.2016
comment
Добро пожаловать, обратите внимание, что массивы имеют основание 0 в C, "korr" — это элемент № 0 в массиве, помните об этом, если хотите: #define KORR 1 - person David Ranieri; 05.09.2016
comment
Я знаю об этом, и я собираюсь использовать тот фиктивный первый элемент, который у вас был до редактирования; так как я хочу, чтобы все команды имели значение, отличное от 0, поэтому я могу использовать if (comm(recieved_ch)){} else{} в некоторых других случаях. На самом деле я инженер HW, и это самый простой способ управления бортовым оборудованием через последовательный порт. Я использую шпатлевку для отправки и получения пакетов данных для отладки. - person L. Zeda; 05.09.2016
comment
Вы также можете использовать X-macros для случаев в коммутаторе, но для доступа к строке... просто поместите NAMECNT в конец перечисления (не в макросе X, потому что это вызовет проблемы), поэтому вы можете сделать printf(%s\n, ((unsigned)index ‹ NAMECNT)? имя ); ... это также позволяет вам использовать -1 для указания недопустимого значения вместо (или в дополнение к) 0 - person technosaurus; 08.09.2016

Я думаю, препроцессор мог бы прояснить ситуацию, используя оператор конкатенации ##, но это не даст преимущества в производительности. Оператор switch может быть оптимизирован компилятором, но это зависит от реализации.

Вместо «одного большого переключателя» используйте массив указателей на функции. Что-то типа

func_ptrs[comm(received_ch) - 1]();

вызовет соответствующую функцию, где foo1 находится в индексе 0, foo2 в индексе 1 и т. д. Чтобы добавить команду, просто добавьте имя команды в список команд и указатель на функцию func_ptrs.

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


Кроме того, линейный поиск по массиву строк довольно неэффективен. Хеш-таблица даст преимущество в производительности.

person cadaniluk    schedule 05.09.2016
comment
Я согласен с указателями функций. Не знаю, как вы ожидали, что ## поможет. Кроме того, я не думаю, что использование хэш-таблицы дает много преимуществ, учитывая, насколько короткими являются список и строки. Если линейный поиск по списку слишком медленный, простой bsearch может быть достаточно, пока список отсортирован. - person user694733; 05.09.2016
comment
Это хорошая идея, спасибо. Я думал о чем-то подобном, но обычно мне не нравится вызывать слишком много функций (что-то, что я получил от старых UC с мелкими стеками). И большинство этих команд будут однострочными (установка констант, inc/dec sfrs, замена битов и т. д.). 20 команд по 10 символов в каждой, так что 200 сравнений, так что это ИМХО достаточно быстро, в конце концов, это интерфейс человека и uC :D - person L. Zeda; 05.09.2016
comment
Я думаю, вы имеете в виду оператор строковой обработки (#), а не оператор вставки токенов (##). Что касается производительности switch по сравнению с индексированием в массив указателей на функции, позвольте мне упомянуть этот мой ответ от некоторого времени назад. - person 5gon12eder; 05.09.2016