Home > front end >  Referencing string array in C struct
Referencing string array in C struct

Time:10-05

I'm developing a simple but flexible menu system for an embedded system in C. Due to limitations of the platform , I want to avoid dynamic allocation and want everything to be defined statically.

I have an Menu type and a MenuItem type defined as structs, with a Menu containing multiple MenuItems. Each menu item will have a Label, ItemOptionType and then a pointer to an array of Options.

That part that's not working for me is creating a reference to the Option array. I get a warning saying

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, &dayOptionList};

Any suggestions on the right approach would be much appreciated.

#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, &dayOptionList};
MenuItem menu_item2 = {"Age", OPTION_NUMERIC, 0, 120, 25, 0, NULL};


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

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

CodePudding user response:

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

In the above declaration the last line is a very special 'flexible array' declaration. It allows having structs of arbitrary sizes in 'c'. Also, whehen you declare any array with '[]', it is not a real pointer, all its members must be present in the struct.

In your case you just need a pointer to a statically allocated array of strings, which can be expressed as

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

This declaration statically allocates array of 7 elements. Compiler does it for you:

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

Array names in 'c' represetn their addresses as well. So, when you pass a pointer to the array in params, you do not need to use &.

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

CodePudding user response:

The error comes from trying to assign a char* to an array pointer, which are not compatible types.


First of all, char* dayOptionList[] is wrong on any system, since string literals are read-only. But it is especially wrong in embedded systems, because you want this table to be allocated in flash, not RAM. Correct code is:

const char* dayOptionList[] = { ...

Now if you have a list of strings like this:

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

Then the sensible member type to use in the struct is a const char**, to point at the first member in the above array. char* optionList[]; declares a flexible array member, which is briefly put, the wrong solution to a different problem.


However, we need to know the size of the array somehow too. This can be done in two ways:

  • Either include a NULL sentinel value at the end of the above array. This is a memory over speed optimization, we can't have trivial direct access, but have to iterate through the container item by item.

  • Or include a separate size member in the struct, which is probably the most sensible thing to do in most use-cases. If we know the size, we can do a direct access to an item in the array without risking out-of-bounds access.


Misc code review unrelated to the question:

  • You should consider your struct layout with alignment in mind, so there will be no needless, wasted padding bytes in the middle. The compiler is not allowed to re-order the struct, so this is the programmer's responsibility.

  • You should stop using the "primitive data types" int, unsigned char etc since those are non-deterministic and therefore non-portable. Professional embedded systems always use the type system of stdint.h.

  • int main(int argc, char *argv[]) is the wrong format for microcontroller embedded systems. Those use impl.defined void main (void) instead. If you are using gcc, compile with -ffreestanding which means embedded system target.

  • Related