Forward declarations of const struct arrays

I'm become a little obsessed with a personal project that, on top of using some substantial libraries, will have an extensive menu system. I'm doing a rough sketch to see if I can fit it on a ATmega328 or if I have to shell out for an mbed.

I'm putting as much of the menu system in the text segment as possible.

typedef struct {
    iconst char MenuType;
    const char *MenuText;
    byte *SubMenu;
    byte payload_index;
    void (*PtrFunctionOnSelection) (void);
}MENU;


const MENU m_main[] =
{
    {MENU_SUB,  "SubMenu1", (MENU *) &m_sub1, 0, NULL},
    {MENU_CMD, "Command1", NULL,                   0, &fp_cmd1},
    {MENU_CMD, "Command2", NULL,                   0, &fp_cmd2},
    ...
};
const MENU m_sub1 []=
{
    {MENU_SET, "SubMenu2",    (MENU *) &m_sub2,  1, NULL},
    ...More stuff...
    {MENU_END, "",                 (MENU *) &m_main, 0, NULL}
};
...

...
setup, etc.

When I try to compile I get a couple of errors that have me scratching my head.
-m_sub1 and m_sub2 were not declared in the scope
-I cannot convert the (MENU ) to m_main to const byte in initialization.

The first error I though I could solve with some forward declarations by adding this above the initializations:

const MENU m_main [];
const MENU m_sub1 [];
const MENU m_sub1 [];

But now I have errors about uninitialized const's and unknown storage sizes.

As for the pointer conversion, a pointer is a pointer no? I always thought the types were purely for our sanity.

Any insights would be appreciated.

Putting the struct defintion in a separate header file (another "tab") should help.

No dice. Though I did catch the second issue by changing the pointer type of subMenu to void.

typedef struct {
    iconst char MenuType;
    const char *MenuText;
    byte *SubMenu;
    byte payload_index;
    void (*PtrFunctionOnSelection) (void);
}MENU;

Huh?

At least post code that demonstrates the problem, without a lot of … in it.

What is iconst?

iconst was a typo in the post. It's hard not throw in an 'i' or a colon here and there when you've been using vim all day.

The rest of the code was irrelevant. 200 lines of the same constant variables. More code would have only served to obfuscate the issue.

The solution was to declare the individual MENU variables in a header file as extern constants.

Does the compiler only make one pass?

As for the pointer conversion, a pointer is a pointer no? I always thought the types were purely for our sanity.

Nope, types are required for the compiler to correctly use pointer based operators

  • Dereference / Indirection
    → Pointer to member
    ->* Pointer to pointer member

See http://arduino.cc/en/Guide/Troubleshooting << Second last item in list

Make sure your extern header variables are initialised in the cpp and only declared in the header.

The source is modified first by arduino then the compiler I assume would only tokenize the files once… Only worked with c1 not gcc

Nope, types are required for the compiler to correctly use pointer based operators

Of course. What I meant was that, when using casts, a pointer is just an address. I was casting it at initialization, when the compiler allocates the memory. Then again I would have had to cast it every time I used it…

typedef struct {
    const char MenuType;
    const char *MenuText;
    byte *SubMenu;       //will point to type MENU but I have no idea how large MENU will be, just allocate space for an address
    byte payload_index;
    void (*PtrFunctionOnSelection) (void);
}MENU;                   //Now know how large MENU is, two bytes + three pointers = 8B

const MENU m_main[] =
{
    {MENU_SUB, "SubMenu1", (MENU *) &m_sub1, 0, NULL}    //m_sub1 points to a byte but as long as I cast on use the compiler should be happy 
}

Granted, not the best way to do things but I don’t see why it’s error worthy as opposed to a warning. Does declaring SubMenu as a pointer to void explicitly tell the compiler to expect a cast on use? Even if I declare “void* SubMenu” in the struct I still have to cast it as a MENU* on use so I have a pointer to 8 Bytes no?

See http://arduino.cc/en/Guide/Troubleshooting << Second last item in list

Thanks. Wish I would have found this yesterday.

oboud:
Even if I declare "void* SubMenu" in the struct I still have to cast it as a MENU* on use so I have a pointer to 8 Bytes no?

No, no, no.

Making a structure that refers to itself is one of the oldest problems in the book. Or should I say The Book? My copy of "The C Programming Language" (2nd edition) by Kernighan and Ritchie describes how to solve it on page 140. The book was printed in 1988.

In your particular case, this compiles:

typedef struct MENU {
    const char MenuType;
    const char *MenuText;
    const MENU *SubMenu;      
    byte payload_index;
    void (*PtrFunctionOnSelection) (void);
};                 

const MENU m_sub1 [] = 
{
      {'1', "Chop", NULL, 0, NULL},  
      {'2', "Cook", NULL, 0, NULL}, 
      {'3', "Wash dishes", NULL, 0, NULL}, 
};

const MENU m_main [] =
{
    {'1', "Prepare food", m_sub1, 0, NULL}, 
};

void setup () {}
void loop () {}

No casting, no taking addresses of things. No extern constants.

One ‘no’ would have been sufficient. Or you could have been helpful and simply said “Struct variables aren’t necessarily continuous.”

In any case you left out the parent reference on the submenu. You can rearrange the initializations all you want but I’m pretty sure you need forward declarations for:

const MENU m_sub1 [] = 
{
      {'1', "Chop", NULL, 0, NULL},  
      {'2', "Cook", NULL, 0, NULL}, 
      {'3', "Main", m_main, 0, NULL}, 
};

const MENU m_main [] =
{
    {'1', "Prepare food", m_sub1, 0, NULL}, 
};

Or you could have been helpful ...

I thought I was being helpful. You could have Googled it. After all the problem has been around for 24 years.

In any case you left out the parent reference on the submenu

What parent reference? It's called "SubMenu". Not "ParentMenu". I assume you are going down a chain of menus.

... you could have been helpful and simply said "Struct variables aren't necessarily continuous."

I don't even know what that means. In what way would that have helped?

I assume you are going down a chain of menus.

I assume you didn't look at the snippit I posted. I overloaded SubMenu. After all we are dealing with limited memory.

const MENU m_sub1 [] = 
{
      {'1', "Chop", NULL, 0, NULL},  
      {'2', "Cook", NULL, 0, NULL}, 
      {'3', "Main", m_main, 0, NULL},       //HERE IT IS!
};

const MENU m_main [] =
{
    {'1', "Prepare food", m_sub1, 0, NULL}, 
};

I thought I was being helpful... I don't even know what that means.

I'll be more explicit. The components of a structure are not necessarily stored in contiguous memory locations. Hence you cannot cast a pointer of general type to a struct.

Moderator edit: Insulting comment removed. (Nick Gammon)

extern const MENU m_main ;

const MENU m_sub1 =
{
{‘1’, “Chop”, NULL, 0, NULL},
{‘2’, “Cook”, NULL, 0, NULL},
{‘3’, “Main”, m_main, 0, NULL}, //HERE IT IS!
};

const MENU m_main =
{
{‘1’, “Prepare food”, m_sub1, 0, NULL},
};

Sigh…
It’s two discussions.
Issue #1:

oboud:
The solution was to declare the individual MENU variables in a header file as extern constants.

oboud:
you left out the parent reference on the submenu. You can rearrange the initializations all you want but I’m pretty sure you need forward declarations

[quote author=Coding Badly link=topic=104282.msg783246#msg783246 date=1336172645]

extern const MENU m_main ;
[/quote]
[/quote]
So you agree that at least one extern is required.

Issue #2:

oboud:
Even if I declare “void* SubMenu” in the struct I still have to cast it as a MENU* on use so I have a pointer to 8 Bytes no?

oboud:
you could have been helpful and simply said “Struct variables aren’t necessarily continuous.”

oboud:
The components of a structure are not necessarily stored in contiguous memory locations. Hence you cannot cast a pointer of general type to a struct.

And the newest issue:

Moderator edit: Insulting comment removed. (Nick Gammon)

So you agree that at least one extern is required.

It is if you want a forward declaration of a const.

The components of a structure are not necessarily stored in contiguous memory locations.

Correct.

Hence you cannot cast a pointer of general type to a struct.

I have no idea what you mean. (but it doesn't matter)

If you have two declared structs whose members point to one another it seems as though it is more than a matter of preference.

[quote author=Coding Badly link=topic=104282.msg783291#msg783291 date=1336176276]
I have no idea what you mean. [/quote]

typedef struct foo{
    byte *bar;         //bar is a pointer to byte 
    int anotherElementInAFoo;
};

const FooUser[] = {
    {(foo *)oof,42}  //not allowed
};

As opposed to

char foo = 'x';
char * bar = &foo;
int ASCIIofFoo = (int *)bar;  //okaly-dokely