【发布时间】:2016-11-27 02:25:55
【问题描述】:
这里是简单的健全性检查问题。基本要求是将两个灵活的数组成员放在一个结构中,以减少对 malloc 的调用次数,以提高性能。
鉴于结构实例是一个对齐的内存块,其中包含多个恒定偏移量的字段,是否可以通过编写偏移计算和强制转换来实现在语义上与结构等效的功能?
void f()
{
typedef struct
{
double x;
char y;
int32_t foo;
double z;
} equivalent;
equivalent * e = malloc(sizeof(equivalent));
free(e);
static_assert(sizeof(equivalent) == 24,"");
char* memory = malloc(24);
double* x = (double*) ( 0 + memory);
char* y = (char *) ( 8 + memory);
int32_t* foo = (int32_t*) (12 + memory);
double* z = (double*) (16 + memory);
free(memory);
}
保持对齐/偏移计算一致是乏味的,但假设类型是不透明的,客户端代码不必看到任何这些。同样,语法开销也被隐藏了。
我已通读 C11(“有效类型”部分)阐明的别名规则,并认为我在那里很清楚。
这是公平的游戏吗?在编写大量非常枯燥的代码之前,我想我会寻求第二个意见。
干杯
编辑:作为对 Jonathan Leffler 的回应,这是我打算如何将几个运行时确定长度的数组放入单个内存块的快速而粗略的草图。
我更喜欢存储一个用于计算数组位置的整数,而不是存储一个已经指向数组的指针,因为它使复制结构更简单。不过,存储适当初始化的指针并在副本上重新定位它们可能会更快。
void* g(uint64_t N_first, uint64_t N_second)
{
// desired representation:
// uint64_t N_first;
// int32_t first[N_first];
// uint64_t N_second;
// double second[N_second];
// this function doesn't populate the arrays, only
// allocates storage and sets up the length fields
uint64_t bytes_for_lengths = 16;
char* bytes = malloc(bytes_for_lengths + bytes_for_first(N_first) +
bytes_for_second(N_second));
uint64_t* ptr_N_first = get_N_first(bytes);
*ptr_N_first = N_first;
uint64_t* ptr_N_second = get_N_second(bytes);
*ptr_N_second = N_second;
return (void*)bytes;
}
// I haven't decided how best to factor out the field access
// and associated functions yet, so this is not optimal
uint64_t* get_N_first(void* vdata)
{
char* data = (char*)vdata;
return (uint64_t*)(data + 0);
}
int32_t* get_first(void* vdata)
{
char * data = (char*)vdata;
return (int32_t*)(data + 8);
}
uint64_t bytes_for_first(uint64_t N_first)
{
// first is an int32_t
// the next field needs to be 8 byte aligned
uint64_t bytes = 4 * N_first;
if (bytes % 8 != 0)
{
bytes += 4;
}
return bytes;
}
uint64_t* get_N_second(void* vdata)
{
uint64_t n_first = *get_N_first(vdata);
uint64_t first_bytes = bytes_for_first(n_first);
char* data = (char*)vdata;
return (uint64_t*)(data + 8 + first_bytes);
}
double* get_second(void* vdata)
{
char * data = (char*)vdata;
uint64_t n_first = *get_N_first(vdata);
uint64_t first_bytes = bytes_for_first(n_first);
return (double*)(data + 8 + first_bytes + 8);
}
uint64_t bytes_for_second(uint64_t N_second)
{
// second is a double
return 8 * N_second;
}
【问题讨论】:
-
你在这里做的很好。尚不清楚您将如何调整它以实现“结构中的两个灵活数组成员”。你不能直接这样做;您可以通过在结构中使用两个指针和连续分配的内存来小心地获得近似结果,但您必须小心。
-
@JonathanLeffler 谢谢。我已经添加了我打算如何适应它的草图。这显然很容易出错,但如果 C 愿意玩得好,这是可以解决的。
-
代码编译后这到底有什么好处?我会假设任何一半体面的编译器都会生成或多或少相同的代码,或者至少与访问 steuct 成员或进行手动偏移的代码一样快。但我可能只是看不到这里的重点
-
uint64_t bytes_for_lengths = 16;2 * sizeof(uint64_t) 等。 -
作为一般规则,您最好避免在界面中使用
void *。您可以将任何指针类型传递给接受void *的函数;你没有类型检查。您最好使用不透明的结构类型(可以使用struct Opaque;声明)并将该类型传递给您的函数。然后尝试传递struct tm *或int *或char *将导致编译警告。或者使用完全定义的结构类型。