在void *

时间:2016-11-27 02:25:55

标签: c types casting c11

这里简单的健全检查问题。基本要求是在结构中放置两个灵活的数组成员,以减少因性能原因而调用malloc的次数。

假设struct实例是包含常量偏移量的多个字段的对齐内存块,是否可以通过编写偏移量计算和转换来实现在语义上等效于结构的功能?

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

2 个答案:

答案 0 :(得分:2)

没有什么是乏味的......

size_t offset_of_x = offsetof(equivalent, x);
size_t offset_of_y = offsetof(equivalent, y);
size_t offset_of_foo = offsetof(equivalent, foo);
size_t offset_of_z = offsetof(equivalent, z);

char* memory = malloc(sizeof(equivalent));
double* x    = offset_of_x   + memory;
char* y      = offset_of_y   + memory;
int32_t* foo = offset_of_foo + memory;
double* z    = offset_of_z   + memory;
free(memory);

是的,这完全合法。

/ edit(编辑后):

而不是使用此表示:

struct fake_your_version {
    uint64_t N_first;
    int32_t first[N_first];
    uint64_t N_second;
    double second[N_second];
};

您应该考虑使用这种表示形式:

struct fake_alternative_1 {
    uint64_t size; // max over all num[i]
    uint64_t num[2]; // num[0] being for first, num[1] being for second
    struct {
        int32_t first;
        double second;
    } entry[num];
};

或此表示:

struct fake_alternative_2 {
    uint64_t num[2];
    void * data[2]; // separate malloc(num[i] * sizeof(whatever));
};

因为您的方法将强制移动除最后一个数组之外的任何尺寸变化的数据。

fake_alternative_1还将保存一个malloc(如果数组需要不同的大小,则以填充字节和丢失内存为代价)。

在你考虑这样做之前,你应该真的问自己,当malloc真的 慢,你必须避免它。也许,无论你做什么,除malloc以外的其他东西都会减慢你的速度(可能,你保存malloc的尝试会让你的代码更慢而不是更快。)

fake_alternative_2只会接受,每个数组都是自己的malloc,但我猜,我不会通过给你这个替代方案告诉你任何新内容。

答案 1 :(得分:0)

我无法帮助,但我觉得使用直接结构来实现双VLA'会更加清晰。结构类型。或多或少是这样的:

// desired representation:
// uint64_t N_first;
// int32_t first[N_first];
// uint64_t N_second;
// double second[N_second];

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

struct DoubleVLA
{
    uint64_t  N_first;
    int32_t  *first;
    uint64_t  N_second;
    double   *second;
    //double    align_32[];     // Ensures alignment on 32-bit
};

extern struct DoubleVLA *alloc_DoubleVLA(uint64_t n1, uint64_t n2);

struct DoubleVLA *alloc_DoubleVLA(uint64_t n1, uint64_t n2)
{
    struct DoubleVLA *dv = malloc(sizeof(*dv) + n1 * sizeof(dv->first) + n2 * sizeof(dv->second));
    if (dv != 0)
    {
        dv->N_first = n1;
        dv->N_second = n2;
        if (alignof(dv->second) >= alignof(dv->first))
        {
            dv->second = (double *)((char *)dv + sizeof(*dv));
            dv->first  = (int32_t *)((char *)dv + sizeof(*dv) + n2 * sizeof(dv->second));
        }
        else
        {
            dv->first  = (int32_t *)((char *)dv + sizeof(*dv));
            dv->second = (double *)((char *)dv + sizeof(*dv) + n1 * sizeof(dv->first));
        }
    }
    return dv;
}

int main(void)
{
    struct DoubleVLA *dv = alloc_DoubleVLA(UINT64_C(11), UINT64_C(32));
    for (uint64_t i = 0; i < dv->N_first; i++)
        dv->first[i] = i * 100 + rand() % 100;
    for (uint64_t j = 0; j < dv->N_second; j++)
        dv->second[j] = j * 1000.0 + (rand() % 100000) / 100.0;
    for (uint64_t i = 0; i < dv->N_first; i++)
        printf("%.2" PRIu64 " = %12" PRId32 "\n", i, dv->first[i]);
    for (uint64_t j = 0; j < dv->N_second; j++)
        printf("%.2" PRIu64 " = %12.2f\n", j, dv->second[j]);
    free(dv);
    return 0;
}

即使在32位平台上,结构末端也应该有足够的填充,以使其大小适合于在结构和数组之后立即对齐double数组。之后int32_t。但是,通过将两个大小放在第一个并且两个指针在结构中最后,可以避免不必要的填充。这在64位平台上不是问题。可选的align_32 VLA假定int32_t的对齐要求不大于double的对齐要求;即使存在一些奇怪的对齐限制或要求,它也能确保结构正确填充。可以提供满足约束条件的静态断言。

alignof材料来自C11;它允许您使用具有不同对齐要求的两种类型,并自动选择更好的布局(在不太严格的布局之前更严格对齐的阵列)。

通过这种组织,不需要结构部分的功能接口。直接访问很简单,很容易理解。