如何使用相同的函数初始化类似的结构?

时间:2016-01-19 01:22:51

标签: c pointers struct

我有一些以void *buffer开头的结构,下一个成员可以是不同的类型:

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

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

来自struct A的缓冲区将存储n个type_x类型的元素,后跟type_y个n个元素。其他成员type_x *xtype_y *y将分别指向这些数组。类似于struct B

我目前正在做的是这样的事情:

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);
}

有没有办法创建一个功能来实现这一目标?该函数应作为参数接收指向其中一个结构(void *s)和int的指针,如下所示:

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

我考虑过的其他一些选择:

  1. 创建void allocate(void *buffer, int n, ...)并指向结构的指针。这个问题是我必须给它void *指针,所以我必须给它每种类型的大小。

  2. 创建void *create_struct(StructType type)(其中StructType是枚举)但我必须为每个结构编写案例代码,我希望能够定义新的结构而不必编写附加代码。< / p>

  3. 我正在尝试这样做,因为我将有许多结构,并且因为allocate函数对每个结构基本上都是相同的,我认为可能有一种“更清洁”的方法。

    另外,我知道我可以删除缓冲区并直接将内存分配给所有成员,但我想这样做,因此数据是连续存储的。

3 个答案:

答案 0 :(得分:1)

没有通用的方法可以在类型安全的情况下快速松散地播放。这意味着,从技术上讲,真正通用的解决方案将导致未定义的行为。如果我被迫实现这样的东西,我将不得不假设我可以将传入的结构指针视为一个指针数组。并且需要传入每种类型的大小。忽略对齐问题,一些未经测试的代码:

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);

显然是hacky和丑陋。

更好的解决方案应该是为每种类型创建一个单独的分配器函数。但是,您可以创建一个宏来帮助自动生成分配器。更多未经测试的代码如下:

#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))

辅助头文件定义并推送CREATE_ALLOCATOR宏使用的一些X宏定义:

#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 :(得分: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;
}   

答案 2 :(得分:0)

这是一种在编译时不需要n的替代方案。

请注意,我并非完全确定此合法性,但我是 合理地确定它是有效的。这里的关键思想是,如果p指向a 正确对齐类型T的内存块,然后((char*)p) + sizeof(T) * N也必须指向 只要它落在一个已分配的块内,就可以正确对齐内存。 只要这是真的,我很确定这必然是合法的,因为工会 bufferAlignment_Hack保证buffer[0]适合所有类型。

即使它是合法的,它仍然是一个黑客,所以我不完全确定我推荐它,但它和&# #39;是一个潜在的选择。

#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's 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's 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;
}