Can a C struct contain a pointer to an array of other structs (with a varying length)

Viewed 98

I will try my best to explain this the best I can. I have been working in C for a bit, but have never needed to push beyond my comfort zone. I really want to try and get this working, it will help maintain a structure in my program that I have been working really hard on.

I have no formal C training though, so I am trying to teach myself as much as I can.

I have one typedef struct, let's call it "struct_A_t", and another typedef struct "struct_B_t". Within "struct_A_t" I want (amongst others) a member pointing to an array of "struct_B_t". I have multiple instances of "struct_A_t" and they don't all have the same array length for "struct_B_t" items.

I understand that passing an array to a fn passes the address to the first element, but I have also come to notice that the a pointer to the array and a pointer to the first element are not necessarily the same. Some of the pointer stuff still gets me though.

The ideal would be for me to be able to put all my instances of "struct_A_t" items in an array and pass this array around my program, changing the values of the struct_B and struct_A members as required.

See the example code below:

My main.h

#include <stdio.h>
#include "stdlib.h"
#include "stdbool.h"
#include "stdint.h"


//Defines fo the array lengths of struct_B_t

#define MyGroup0_Length     10
#define MyGroup1_Length     20
#define MyGroup2_Length     30

#define GroupCount 3

//Struct B definition
typedef struct
{
    bool        ItemSelected;
    uint8_t     ItemID;
    uint8_t     ItemState;
} struct_B_t;

//Struct A Definition
typedef struct 
{
    bool        GroupEnabled;
    uint8_t     GroupID;
    uint8_t     GroupState;
    uint8_t     ItemCount;
    struct_B_t  (*ItemList)[];
} struct_A_t;

//These are the item lists that must be contained within each GroupState
struct_B_t Group0Items[MyGroup0_Length];
struct_B_t Group1Items[MyGroup1_Length];
struct_B_t Group2Items[MyGroup2_Length];


//Function prototypes
void main(void);
void BuildGroups(struct_A_t* _groups);
void ChangeGroupItems(struct_A_t* _groups, uint8_t _groupCount);

My main.c

#include <stdio.h>
#include "stdlib.h"
#include "stdint.h"
#include "stdbool.h"
#include "main.h"

//A array of type "struct_A_t" with length = 3, to accomodate my three groups
struct_A_t MyGroups[GroupCount];

void main(void)
{
    BuildGroups(&MyGroups);
    
    ChangeGroupItems(&MyGroups, GroupCount);
}

void BuildGroups(struct_A_t* _groups)
{
    //Set the group lists
    
    //Group 1
    _groups[0].ItemList = &Group0Items;
    _groups[0].ItemCount = MyGroup0_Length;
    
    //Group 2 
    _groups[1].ItemList = &Group1Items;
    _groups[1].ItemCount = MyGroup1_Length;
    
    //Group 3
    _groups[2].ItemList = &Group2Items;
    _groups[2].ItemCount = MyGroup2_Length;
}

void ChangeGroupItems(struct_A_t* _groups, uint8_t _groupCount)
{
    for(uint8_t i = 0; i < _groupCount; i++)
    {
        for(uint8_t j = 0; j < _groups[i].ItemCount; j++)
        {
            if(_groups[i].ItemList[j].ItemSelected)
            {
                _groups[i].ItemList[j].ItemState++;
            }
        }
    }
}

As you can guess, this is not compiling correctly. When I change things around a bit and do get this to build, I get warnings for incompatible pointer types.

I don't feel like this is an extremely unique problem, so I would love to hear any suggestions. At this stage, I am trying to keep my application data confined to one struct, and I want to avoid making too many things globally accessible.

I also should note, my actual program looks a bit different, and things are split into more .c files than this, and what I am trying to achieve will fit nicely into my overall program pattern.

Thanks in advance.

4 Answers

When you use an array, it decays to a pointer to the first element. For example when you use Group1Items[n]++, which is equivalent to (*(Group1Items+n))++, Group1Items will decay to a pointer to Group1Items[0], after that the indexing of the array is done to access the n'th element which is then incremented. This part: [n] is an operation on a pointer, a pointer that points to the Group1Items-array.

This 2 things are equivalent:

struct_B_t myArray[3];
//myArray decays to a pointer,
//which then access the second element in myArray
myArray[1].ItemState=0; 

// .. some code goes here

myArray[1].ItemState++ 

and

struct_B_t myArray[3];
//here myArray decays to a pointer which has the 
//same type as myPointer.
struct_B_t *myPointer = myArray; 
myPointer[1].ItemState=0;

// .. some code goes here

myPointer[1].ItemState++ 

Note that the array does not decay to a pointer when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array. (Thanks @chux - Reinstate Monica)

You can have a pointer to an array of unknown length. (Note that an unknown length is different from a variable length. int a[] declares an array with unknown length. int a[n] declares an array with a variable length, presuming n is some variable.) However, it is not necessary in the circumstance you describe. Generally, we simply use a pointer to the first element.

If, in struct_A_t, the ItemList member is declared as struct_B_t *ItemList, it will be a pointer to a struct_B_t. Once you set it to point to the first element of an array of struct_B_t, you may then use t.ItemList[i] to access element i of the array, given that t is some struct_A_t (such as _groups[0]).

This is not a unique problem.

Do not declare main as void main(void). Use int main(void) or int main(int argc, char *argv[]) or some other form that your C implementation defines.

Do not declare main in main.h. In a header file, put only declarations for the things its associated source file provides for other source files to use. However, main is an exception; the C implementation, in effect, already has its own declaration of main. You only need to define it, not separately declare it.

Change:

#include "stdlib.h"
#include "stdint.h"
#include "stdbool.h"

to:

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

Do not use names beginning with an underscore. These are reserved for use (by the C standard library and compilers and other parts of the implementation of C) with file scope.

Array variable/field in C in most cases is equal to pointer to its memory. So you can use the next struct definition:

typedef struct 
{
    bool        GroupEnabled;
    uint8_t     GroupID;
    uint8_t     GroupState;
    uint8_t     ItemCount;
    struct_B_t  *ItemList;
} struct_A_t;

And then init its items with your arrays:

 //Group 1
 _groups[0].ItemList = Group0Items;
 _groups[0].ItemCount = MyGroup0_Length;
    
 //Group 2 
 _groups[1].ItemList = Group1Items;
 _groups[1].ItemCount = MyGroup1_Length;

Note that I assign array directly to pointer.

Work with such structures remains the same:

_groups[i].ItemList[j].ItemState++;

I would personally use:

typedef struct 
{
    bool        GroupEnabled;
    uint8_t     GroupID;
    uint8_t     GroupState;
    uint8_t     ItemCount;
    struct_B_t  ItemList[];
} struct_A_t;

only one malloc or realloc is needed to allocate the memory. One free to free it.

Related