C of the same structure of different sizes
My question is related to this: c define arrays in a structure with different sizes
However, I don't want to use dynamic placement (inline target).
- Summary of the problem:
In C, I want to have two versions of the same structure, each with different sizes for their static arrays. Both structures will be used by the same functions via a pointer parameter.
typedef struct {
short isLarge; //set 0 at initialization
short array[SIZE_A];
//more arrays
} doc_t;
typedef struct {
short isLarge; //set 1 at initialization
short array[SIZE_B];
//more arrays
} doc_large_t;
void function( doc_t* document ) {
if ( document->isLarge ) {
//change document into doc_large_t* [1]
}
//common code for both doc_t and doc_large_t
}
- Questions:
(1) The above description needs a way to dynamically convert a doc_t * pointer into a doc_large_t * [1] document. Is it possible? How?
(2) Another solution I came up with is to have a common header data structure for both structures, including not only the isLarge flag, but also pointers to the following static arrays. How ugly is that?
(3) Also, do you have a nice trick or desktop I could use?
EDIT:
- More context:
My app is to find paths on the embedded MCU.
I have geometric entities like polygons. Polygons can describe simple rectangular obstacles as well as more complex shapes (such as an accessible area).
Complex polygons can have a huge number of vertices, but they are few. Simple polygons are very common.
Both will use the same algorithms. I know in advance which polygon will need more vertices.
What I am trying to do is optimize the working memory to fit into the MCU. (i.e. small forms get small arrays, complex ones get large arrays)
source to share
Idea similar to what you mentioned in your question already (pointers to arrays), but with one single pointer:
typedef struct
{
short array[SIZE_B - SIZE_A];
// more arrays alike...
} Extension;
typedef struct
{
short array[SIZE_A];
//more arrays (all the small ones!)
Extension* extraData;
} doc_t;
If extraData is NULL, you have a small polygon, otherwise you will find additional data in the specified structure. Adopted, iterating over all values ββfor large polygons gets a little nasty ...
If you can use predefined size global arrays for each object type (as suggested by Dominic Gibson - a good suggestion by the way), you can save the isLarge flag by replacing it with a function:
int isLarge(void* ptr)
{
return
(uintptr_t)globalLargeArray <= (uintptr_t)ptr
&&
(uintptr_t)ptr < (uintptr_t)globalLargeArray + sizeof(globalLargeArray);
}
Of course, all polygons (in the next case: large, at least) would have to live in this array to make it work. If you create at least one dynamically or otherwise in a different place (stack, other global variable) - we are missing ...
source to share
You should try to keep one structure, and for different array sizes, put them in union
. I don't know if the following structure would make sense for your case.
typedef struct {
short isLarge; //manually set to 0 or 1 after creating structure
//and accordingly initialize the arrays in below union
union my_varying_arrays {
short array_A[SIZE_A];
short array_B[SIZE_B];
};
//more arrays
} doc_t;
If isLarge
- 0
, set the value for the array array_A
, and if 1
set the value for the array array_B
.
source to share
You can do this const
by using data void *
for a specific array. Then you just throw void *
in what you need to depend on the attributes in the structure.
It gets more complicated when you need runtime structures. Especially for embedded targets.
typedef struct {
short size;
void *array;
} doc_t;
Where array
points to a block of memory allocated by the memory manager.
Now you need to decide whether to use the C standard malloc
or use some unified memory system based on the largest block size. An example is ChibiOS Memory Pools . If you randomly allocate and free blocks of memory of variable size, you run the risk of memory fragmentation.
If you allocate in stages, you don't have to worry a lot about memory. Just create one big block and keep track of where you are. A bit like a stack.
source to share
After editing, I think the best thing you can do is profile your needs by defining the maximum simple and complex polygons that your target can handle, and then declare a pool of simplexes and common polygons, like this:
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#define MAX_COMPLEX 16
#define MAX_SIMPLE 16
uint16_t g_Simple_Poly_set[MAX_COMPLEX][SIZE_A];
uint16_t g_Complex_Poly_set[MAX_COMPLEX][SIZE_B];
uint16_t g_Simple_Poly_used = 0;
uint16_t g_Complex_Poly_used = 0;
struct poly
{
bool isLarge;
uint16_t *vetexes;
};
bool create_poly_simple (struct poly *p)
{
bool retVal = false; // default: not more space for poly
if (g_Simple_Poly_used < MAX_SIMPLE)
{
p->isLarge = false;
p->vetexes = &g_Simple_Poly_set[g_Simple_Poly_used][0];
g_Simple_Poly_used++;
retVal = true;
}
return retVal;
}
bool create_poly_compleX (struct poly *p)
{
bool retVal = false; // default: not more space for poly
if (g_Complex_Poly_used < MAX_COMPLEX)
{
p->isLarge = true;
p->vetexes = &g_Complex_Poly_set[g_Complex_Poly_used][0];
g_Complex_Poly_used++;
retVal = true;
}
return retVal;
}
void your_stuff_with_poly ( struct poly *p)
{
uint32_t poly_size = (p->isLarge == false) ? SIZE_A : SIZE_B;
// your stuff with the correct size
}
This is a simple implementation designed to statically "instantiate" structures. You can also improve the code with the create / destroy function, which keeps track of which array in the pool can be used for free.
source to share
Your decision number 2 is the right idea. I don't understand why you think this is ugly. Perhaps this beautiful implementation will change your mind.
You can implement single C inheritance by placing the underlying structure as the first member of the inheriting structure. The inheriting objects can then reference a pointer to the base type.
typedef struct {
short doc_type;
short *array_ptr;
// more array pointers
} doc_base_t;
typedef struct {
doc_base_t base; // base.doc_type set 0 at initialization
short array[SIZE_A]; // base.array_ptr initialized to point here
//more arrays
} doc_small_t;
typedef struct {
doc_base_t base; // base.doc_type set 1 at initialization
short array[SIZE_B]; // base.array_ptr initialized to point here
//more arrays
} doc_large_t;
void function( doc_base_t* document ) {
if ( document->doc_type == 1) {
// array size is large
} else {
// array size is small
}
//common code referencing arrays through doc_base_t->array_ptr
}
The member array_ptr
is doc_base_t
not needed for the inheritance mechanism. But I added this specifically for the "common code" part of your function. If doc_base_t
not included array_ptr
, then you could cast generic document
on type doc_small_t
or doc_large_t
value based base_type
. But then you might need a different implementation for each inherited type. By adding a member array_ptr
to doc_base_t
, I suspect you can write a generic implementation for all inherited types.
So, you statically declare all your instances doc_small_t
and doc_large_t
. And initialize the elements base.doc_type
and base.array_ptr
when you initialize each object. Then you call both types of objects in doc_base_t before calling function
. (Or pass the address of the member base
, which results in the same pointer value.)
Updated example:
static doc_small_t doc_small_instances[NUM_SMALL_INSTANCES];
static doc_large_t doc_large_instances[NUM_LARGE_INSTANCES];
// DocInit must be called once at startup to initialize all the instances.
void DocInit()
{
int index;
for (index = 0; index < NUM_SMALL_INSTANCES; index++)
{
doc_small_instances[index].base.doc_type = SMALL;
doc_small_instances[index].base.array_ptr = doc_small_instances[index].array;
}
for (index = 0; index < NUM_LARGE_INSTANCES; index++)
{
doc_large_instances[index].base.doc_type = LARGE;
doc_large_instances[index].base.array_ptr = doc_large_instances[index].array;
}
}
// DocProcess processes one doc, large or small.
void DocProcess(doc_base_t *document)
{
int index;
short *array_member_ptr = document->array_ptr;
int array_size = SMALL;
if (document->doc_type == LARGE)
{
array_size = LARGE;
}
for (index = 0; index < array_size; index++)
{
// Application specific processing of *array_member_ptr goes here.
array_member_ptr++;
}
}
// ProcessAllDocs processes all large and small docs.
void ProcessAllDocs(void)
{
int index;
for (index = 0; index < NUM_SMALL_INSTANCES; index++)
{
DocProcess(&doc_small_instances[index].base);
}
for (index = 0; index < NUM_LARGE_INSTANCES; index++)
{
DocProcess(&doc_large_instances[index].base);
}
}
source to share
This is easy with malloc()
dynamic placement or similar methods. Just use a flexible array element:
typedef struct {
short isLarge; //set 0 at initialization
.
.
.
short array[SIZE_A];
short largeArray[];
} doc_t;
To highlight the "small structure":
doc_t *small = malloc( sizeof( *small ) );
small->isLarge = 0;
To highlight the "big structure":
doc_t *large = malloc( sizeof( *large ) + ( SIZE_B - SIZE_A ) * sizeof( large->largeArray[ 0 ] );
large->isLarge = 1;
Note that you must keep the element largeArray
last, which means the element array
must be next for the last one for this to work.
Depending on how you do your own allocation, this may or may not be applicable.
(It's also a bit of a hack, as it depends on being able to access data at largeArray
an index SIZE_A
or more array
. This is access to an object outside of its bounds ...)
source to share