How can I initialize similar structures using the same function?

I have some structures starting with void *buffer, and the following members can be of different types:

struct A {
    void *buffer;
    type_x *x;
    type_y *y;
};

struct B {
    void *buffer;
    type_w *w;
    type_y *y;
    type_z *z;
};

A buffer from struct Awill store n elements of the type type_x, followed by n elements type_y. The remaining members type_x *xand type_y *ywill point to these arrays, respectively. Similarly for struct B.

What I'm doing right now looks something like this:

void allocate(struct B **b, unsigned int n) {
    (*b)->buffer = calloc(n * (sizeof(type_w) + sizeof(type_y) + sizeof(type_z));

    (*b)->w = (type_w *) (*b)->buffer;
    (*b)->y = (type_y *) ((*b)->w + n);
    (*b)->z = (type_z *) ((*b)->y + n);
}

Is there a way to create a function to achieve this? The function must take as arguments a pointer to one of these structures ( void *s) and int, for example:

void allocate(void *s, unsigned int n) {
    // MAGIC
}

Some other options that I have considered:

  • void allocate(void *buffer, int n, ...) . , void *, .

  • void *create_struct(StructType type) ( StructType - ), , , .

, , allocate , , " " .

, , , , .

+4
3

, . , , undefined. - , , . . , :

void allocate(void *sp, size_t n, ... /* terminate with 0 */) {
    void **sv = sp;
    size_t arg, total = 0;
    size_t args = 0;
    va_list ap;
    va_start(ap, n);
    while ((arg = va_arg(ap, size_t)) != 0) {
        total += arg;
        ++args;
    }
    va_end(ap);
    *sv = calloc(...);
    sv[1] = sv[0];
    va_start(ap, n);
    while (--args > 0) {
        ++sv;
        sv[1] = (char *)sv[0] + va_arg(ap, size_t);
    }
    va_end(ap);
}

allocate(a, n, sizeof(type_x), sizeof(type_y), (size_t)0);
allocate(b, n, sizeof(type_w), sizeof(type_y), sizeof(type_z), (size_t)0);

.

. , . :

#define CREATE_ALLOCATOR(Type, X_Fields) \
void allocate_##Type (struct Type *sp, size_t n) { \
    _Pragma("pop_macro(\"X\")") \
    size_t total = 0 \
        X_Fields \
        ; \
    void *p; \
    sp->buffer = calloc(sizeof(*sp) + total); \
    p = sp->buffer; \
    _Pragma("pop_macro(\"X\")") \
    X_Fields \
    ; \
}

#include "create_allocator_helper.h"
CREATE_ALLOCATOR(A, X(x) X(y))
#include "create_allocator_helper.h"
CREATE_ALLOCATOR(B, X(w) X(y) X(z))

X, CREATE_ALLOCATOR:

#ifdef X
#undef X
#endif
#define X(A) ; sp->A = p; p = sp->A + n
#pragma push_macro("X")
#undef X
#define X(A) + sizeof(sp->A)
#pragma push_macro("X")
#undef X
+1

n , ( )/( ) , .

#include <stddef.h>

/*  
We put a type marker at the beginning of each struct.  There won't be padding before the first member, and all the types
start with a struct Type, so we can do `(struct Type*)&unknown_structure` and be guaranteed to have a valid object that
tells us what type the rest of it is.

In practice, I'd probably use X-Macros to generate an enum containing all the types instead of using strings, to
make comparison faster 
*/

struct Type { char *type; size_t n; };

/* We define what types of arrays each structure contains. Since the struct contains the arrays themselves
instead of pointers to them, the memory will be contiguous, +/- a bit of padding. */

#define DECL_A(N) struct A_##N { struct Type type; char x[N]; double y[N]; }
#define DECL_B(N) struct B_##N { struct Type type; size_t n; int x[N]; float y[N]; char z[N]; }

/* 
Declare a struct and initialize the type and n members.  This one just
declares a local variable, but we could make a malloc version easily enough. 
*/
#define CREATE_STRUCT(NAME, TYPE, N) struct TYPE##_##N NAME = { .type = { #TYPE, N} }

/* We declare all struct type/size combinations we'll use */
DECL_A(42);
DECL_A(100);
DECL_B(30);

int main(void) {
    int i;

    CREATE_STRUCT(foo, A, 42);
    CREATE_STRUCT(bar, A, 100);
    CREATE_STRUCT(baz, B, 30);

    return 0;
}   
+1

, , n .

, , , . , p T, ((char*)p) + sizeof(T) * N , . , , , buffer Alignment_Hack , buffer[0] .

, , , , .

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

/* This MUST contain all types that might be stored in the arrays */
union Alignment_Hack {
    short hd;
    int d;
    char c;
    float hf;
    double f;
};

/* This struct is used for *all* structures.  The structure-specific details get specified later */
struct Variable_Structure {
    size_t num_arrays;
    void **arrays;
    union {
        union Alignment_Hack *hack;
        char *buffer;
    } u; //u.buffer[0] is guaranteed to be properly aligned for anything in the union
};

//Here where the details for a specific struct (struct A {short x; double y; int z; }) are specified.
size_t sizes_A[] = { sizeof(short), sizeof(double), sizeof(int) };


void create_structure(struct Variable_Structure *p, const size_t array_count, size_t *sizes, unsigned nvars) {
    size_t offsets[nvars];//in bytes (NOTE: requires C99)
    unsigned i;

    offsets[0] = 0;
    for (i = 1; i < nvars; i++) {
        //offsets[i] must be an even multiple of sizes[i] and must also be past the end of the last array
        size_t min = offsets[i - 1] + sizes[i - 1] * array_count;
        size_t mod = min % sizes[i];

        //offsets[i] = min_p such that p >= min and p % sizes[i] == 0
        offsets[i] = (min - mod) + (mod ? sizes[i] : 0);// (min - mod) and (min - mod + sizes[i]) are the two possible starting points

        /* Visualization of the transition from TYPE_A[] to TYPE_B[], showing where everything pointing:

                                      min (end of TYPE_A array)
                                       V
        ... | TYPE_A | TYPE_A | TYPE_A |
        ...   |  TYPE_B  |  TYPE_B  |  TYPE_B  |  TYPE_B  |
                                    ^          ^
                             min - mod       (min - mod) + sizes[i] */

        assert(offsets[i] >= min);//doesn't overlap previous array
        assert(offsets[i] <= min + sizes[i]);//doesn't include more padding than necessary
        assert(0 == offsets[i] % sizes[i]);//alignment correct
    }
    size_t bufsiz = offsets[nvars - 1] + sizes[nvars - 1] * array_count;

    //Skipping error checking for brevity
    p->num_arrays = nvars;
    p->u.buffer = malloc(bufsiz);
    p->arrays = malloc(sizeof(void*) * nvars);
    for (i = 0; i < nvars; i++) {
        p->arrays[i] = p->u.buffer + offsets[i];
    }
}

void free_structure(struct Variable_Structure *p) {
    free(p->arrays);
    free(p->u.buffer);
}

int main(void) {
    struct Variable_Structure a;

    size_t n = 42;
    create_structure(&a, n, sizes_A, sizeof(sizes_A)/sizeof(*sizes_A));

    unsigned i;
    for (i = 0; i < n; i++) {
        //We could always set up some macros or something so we could say, e.g., A(x, i) instead of ((short*)a.arrays[0])[i]
        ((short*)a.arrays[0])[i] = i;
        ((double*)a.arrays[1])[i] = i;
        ((int*)a.arrays[2])[i] = i;

        printf("%hd %f %d\n",
            ((short*)a.arrays[0])[i],
            ((double*)a.arrays[1])[i],
            ((int*)a.arrays[2])[i]);
    }

    printf("SIZES: %zu %zu %zu\n", sizeof(short), sizeof(double), sizeof(int));
    printf("OFFSETS: %p %p %p\n", a.arrays[0], a.arrays[1], a.arrays[2]);

    free_structure(&a);
    return 0;
}
0

Source: https://habr.com/ru/post/1624883/


All Articles