我有一些以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 *x
和type_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
}
我考虑过的其他一些选择:
创建void allocate(void *buffer, int n, ...)
并指向结构的指针。这个问题是我必须给它void *指针,所以我必须给它每种类型的大小。
创建void *create_struct(StructType type)
(其中StructType是枚举)但我必须为每个结构编写案例代码,我希望能够定义新的结构而不必编写附加代码。< / p>
我正在尝试这样做,因为我将有许多结构,并且因为allocate函数对每个结构基本上都是相同的,我认为可能有一种“更清洁”的方法。
另外,我知道我可以删除缓冲区并直接将内存分配给所有成员,但我想这样做,因此数据是连续存储的。
答案 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
也必须指向
只要它落在一个已分配的块内,就可以正确对齐内存。
只要这是真的,我很确定这必然是合法的,因为工会
buffer
和Alignment_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;
}