2

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.

  • 1
    `struct_B_t *ItemList` and `_groups[0].itemList = Group0Items;` – user3386109 Jul 22 '21 at 19:41
  • 2
    Replace `struct_B_t (*ItemList)[];` with `struct_B_t *ItemList;` – 12431234123412341234123 Jul 22 '21 at 19:43
  • Are you working on a microcontroller? If so, which one? Not necessarly relevant for the question, but `void main(void)` shouldn't be used on hosted environments. That and your use of `uint8_t` let think you are on a free standing environment? – 12431234123412341234123 Jul 22 '21 at 19:48
  • @12431234123412341234123 *Don't start names with an `_`, they are reserved.* The C standard only *fully* [reserves identifiers that start with two underscores or one underscore and a capital letter](https://port70.net/~nsz/c/c11/n1570.html#7.1.3). Single underscores followed by a lower-case letter are OK as local variables or function parameters. Never starting a variable with an underscore does make it much easier to avoid potential conflicts. – Andrew Henle Jul 22 '21 at 19:55
  • Hi, thanks for the reply. I am working on a custom project on an stm32. If I were to replace the struct_B_t itemlist definition, will I still be able to use and access it as an array? Thanks for the tips, I was quickly trying to get a working example up and running, but will definitely note that for future reference. – Jacques Duminy Jul 22 '21 at 19:55
  • @JacquesDuminy There is no way to access anything in C as an array. But yes, you can use `[n]` on a pointer to access the `n`'th element in the array that starts at the address which the pointer points to. – 12431234123412341234123 Jul 22 '21 at 20:06

4 Answers4

2

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)

  • Detail: "does not decay to a pointer" in other cases too: "Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array," – chux - Reinstate Monica Jul 22 '21 at 20:20
0

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

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++;
Alexander Ushakov
  • 5,139
  • 3
  • 27
  • 50
0

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.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Flexible array member is less universal concept: https://stackoverflow.com/questions/14643406/whats-the-need-of-array-with-zero-elements For example it must be the last element of struct – Alexander Ushakov Jul 22 '21 at 20:18