Ссылка на массив строк в структуре C

#arrays #c #string #struct

Вопрос:

Я разрабатываю простую , но гибкую систему меню для встроенной системы в C. Из-за ограничений платформы я хочу избежать динамического распределения и хочу, чтобы все определялось статически.

У меня есть тип меню и тип элемента меню, определенные как структуры, с меню, содержащим несколько элементов меню. Каждый пункт меню будет иметь метку, тип элемента, а затем указатель на массив опций.

Та часть, которая для меня не работает, — это создание ссылки на массив опций. Я получаю предупреждение со словами

 warning: incompatible pointer types initializing 'char *' with an expression of type 'char *(*)[3]' [-Wincompatible-pointer-types]
MenuItem menu_item1 = {"Day", OPTION_LIST, 0,0,0, 4, amp;dayOptionList};
 

Любые предложения по правильному подходу будут высоко оценены.

 #define MAX_MENU_ITEMS  16


typedef enum {OPTION_LIST, OPTION_NUMERIC, OPTION_BINARY} ItemOptionType;

typedef struct MenuItem {
    char* label;
    ItemOptionType optionType; 
    unsigned char min;
    unsigned char max;
    unsigned char value;
    unsigned char optionCount;
    char* optionList[];
} MenuItem;

typedef struct Menu {
    unsigned char count;
    MenuItem* items[MAX_MENU_ITEMS];
} Menu;


unsigned int MenuAddItem(Menu* menu, MenuItem* item) {
    if (menu->count < MAX_MENU_ITEMS) {
        menu->items[menu->count] = item;
        menu->count  ;
        return 1;
    }
    
    return 0;
}

char* dayOptionList[] = {"Monday", "Tuesday", "Wednesday" "Thursday", "Friday", "Saturday", "Sunday"};

Menu mainMenu;
MenuItem menu_item1 = {"Day", OPTION_LIST, 0,0,0, 4, amp;dayOptionList};
MenuItem menu_item2 = {"Age", OPTION_NUMERIC, 0, 120, 25, 0, NULL};


int main(int argc, char *argv[]) {

    MenuAddItem(amp;mainMenu, amp;menu_item1);
    MenuAddItem(amp;mainMenu, amp;menu_item2);
    while(1);
}
 

Комментарии:

1. char* optionList[]; является гибким элементом массива . Вы не можете получить их без динамического распределения. То, что вы, вероятно, хотите, это что-то вроде char ** . А затем инициализируйте optionList с помощью простого dayOptionList (без оператора указателя toi * ).

2. Кстати, полученная вами ошибка (или предупреждение) не соответствует показанному вами коду, если предположить, что вы получили его при инициализации menu_item1 . Пожалуйста, скопируйте и вставьте полный и завершенный вывод сборки из кода, который вы на самом деле показываете.

3. Ну, у вас могут быть гибкие элементы массива без выделения кучи, но тогда выделение памяти должно обрабатываться вручную путем настройки разделов памяти по фиксированным адресам и т. Д. Обычно это очень громоздкое решение, я сделал это в одном проекте, но не был доволен результатами — слишком много дополнительной сложности.

4. Я исправил образец с полным выводом компилятора. Извини за это.

Ответ №1:

 typedef struct mmMenuItem {
   ...
   char* optionList[];
} MenuItem;
 

В приведенном выше объявлении последняя строка является очень специальным объявлением «гибкий массив». Это позволяет иметь структуры произвольных размеров в «c». Кроме того, когда вы объявляете какой-либо массив с помощью ‘[]’, он не является реальным указателем, все его члены должны присутствовать в структуре.

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

 typedef struct mmMenuItem {
   ...
   char** optionList;
} MenuItem;
 

Это объявление статически выделяет массив из 7 элементов. Компилятор делает это за вас:

 char* dayOptionList[] = {"Monday", "Tuesday", "Wednesday" "Thursday", "Friday", "Saturday", "Sunday"};
 

Имена массивов в «c» также представляют их адреса. Таким образом, когда вы передаете указатель на массив в парах, вам не нужно использовать . amp;

 MenuItem menu_item1 = {"Day", OPTION_LIST, 0,0,0, 4, dayOptionList};
                                                   ^^^^^
 

Ответ №2:

Ошибка возникает при попытке назначить a char* указателю массива, которые не являются совместимыми типами.


Прежде всего, char* dayOptionList[] это неправильно в любой системе, так как строковые литералы доступны только для чтения. Но это особенно неправильно во встроенных системах, потому что вы хотите, чтобы эта таблица размещалась во флэш-памяти, а не в оперативной памяти. Правильный код-это:

 const char* dayOptionList[] = { ...
 

Теперь, если у вас есть список строк, подобных этому:

 const char* dayOptionList[] = {"Monday", "Tuesday", "Wednesday" "Thursday", "Friday", "Saturday", "Sunday"};
 

Тогда разумным типом элемента , который следует использовать в структуре, является a const char** , чтобы указать на первый элемент в приведенном выше массиве. char* optionList[]; объявляет гибкий элемент массива, который, кратко говоря, является неправильным решением другой проблемы.


Однако нам тоже нужно как-то узнать размер массива. Это можно сделать двумя способами:

  • Либо включите NULL значение sentinel в конце приведенного выше массива. Это оптимизация памяти по скорости, у нас не может быть тривиального прямого доступа, но мы должны перебирать контейнер по элементам.
  • Или включите в структуру отдельный элемент размера, что, вероятно, является наиболее разумным в большинстве случаев использования. Если мы знаем размер, мы можем получить прямой доступ к элементу в массиве, не рискуя получить доступ за его пределы.

Разное обзор кода, не связанный с вопросом:

  • Вы должны продумать макет своей структуры с учетом выравнивания, чтобы в середине не было ненужных, потраченных впустую байтов заполнения. Компилятору не разрешается изменять порядок структуры, поэтому ответственность за это несет программист.
  • Вам следует прекратить использовать «примитивные типы данных» int и т. unsigned char Д. , Поскольку они недетерминированы и, следовательно, непереносимы. Профессиональные встраиваемые системы всегда используют систему типов stdint.h .
  • int main(int argc, char *argv[]) это неправильный формат для встроенных систем микроконтроллеров. Вместо этого они используют impl.defined void main (void) . Если вы используете gcc, скомпилируйте, -ffreestanding что означает цель встроенной системы.

Комментарии:

1. Спасибо за отзыв. Что касается обзора кода misc. Я разрабатываю библиотеку на настольном компьютере, прежде чем перенести ее в микроконтроллер, чтобы упростить отладку, поэтому использую int main(int argc, char *argv[]). Но есть и другие замечательные моменты. Внесу эти изменения.

2. @Dibly Вы также можете использовать -ffreestanding на gcc для ПК, если я правильно помню.