指针联合体的动态分配 - C语言

5

我正在使用C语言编写一个基础图像处理库。我知道还有其他(非常好的)库存在,但这对我来说是一次学习经历,而不是达到目的的手段。

我已经定义了以下简化的结构(针对本问题):

union img_rawdata{
    uint8_t*  u8;
    uint16_t* u16;
    float*    flt;
};

enum img_type{
    IMG_U8,
    IMG_U16,
    IMG_FLT
};

struct image{
    union img_rawdata  rawdata;
    enum img_type      type;
    unsigned int       num_values;
};

我的问题是:在union中动态分配正确指针的首选方式是什么?
目前,我唯一看到的方法是使用switch语句,例如:
void alloc_img(struct image* img, enum img_type type, unsigned int num_vals){
    switch (type){
        case IMG_U8:
            img->rawdata.u8 = (uint8_t*)malloc(num_vals*sizeof(uint8_t));
        break;            
        case IMG_U16:
            img->rawdata.u16 = (uint16_t*)malloc(num_vals*sizeof(uint16_t));
        break;
        case IMG_FLT:
            img->rawdata.flt = (float*)malloc(num_vals*sizeof(float));
        break;
    }
}

这看起来并不那么糟糕。然而,在我的实现中,实际的内存分配大约有50行代码(因为rawdata不是一维的,还需要进行错误检查等操作)。

有没有预处理器的魔法可以减少代码冗余,或者这是编写此代码的最佳方式?

或者,另一种完全不同的方法能够避免这个问题吗?


3
不需要对 malloc() 的返回值进行强制类型转换,这样做是多余的,而且可能会隐藏编译器本来可以检测到的错误。 - pmg
你是否知道在某些体系结构中,指针的大小可能因所指向的类型而异?如果您计划跨平台使用,则此联合img_rawdata可能不是一个好主意。 - Étienne
为什么联合体不是一个好主意?据我所知,只要使用正确的成员联合,您就可以成功地解除联合体中每个指针的引用,而不受大小的影响。 - Glenn
嗯,没事,我以为你想要分配联合体的一个成员并使用另一个成员进行取消引用。你使用联合体而不是结构体只是为了节省内存吗? - Étienne
1
联合体提供了一种通用的图像类型,可以容纳不同的类型。关于您最初的陈述,下面的一些答案将比上面显示的解决方案不太可移植,因为它们依赖于指针具有相似的大小。 - Glenn
显示剩余3条评论
2个回答

6
[假设包括 void * 在内的所有指针类型大小相同]
将你所拥有的内容修改如下。
union img_rawdata {
  void * untyped;
  uint8_t * u8;
  uint16_t * u16;
  float * flt;
};

enum img_type {
  IMG_UNDEF = -1
  IMG_U8 = 0,
  IMG_U16,
  IMG_FLT,
  IMG_MAX
};

并添加

const size_t img_type_size[IMG_MAX] = {
  sizeof(*((union img_rawdata *) NULL)->u8),
  sizeof(*((union img_rawdata *) NULL)->u16),
  sizeof(*((union img_rawdata *) NULL)->flt)
};

然后用以下内容替换开关:

assert(IMG_UNDEF < type && IMG_MAX > type);
img->rawdata.untyped = malloc(num_vals * img_type_size[type]);

sizeof((union img_rawdata *) NULL)->u8) 对我来说是新语法。这与 sizeof((union img_rawdata) NULL).u8) 相比有什么优势吗? - Glenn
是的,您无法将其转换为聚合类型。@Glenn - alk
好的,谢谢您的解释。我最后一个问题是:这样构造它与另一个答案中提出的 switch 相比是否有固有优势? - Glenn
等等,sizeof(((union img_rawdata *) NULL)->u8) 不会返回 uint8_t 的大小,而是会返回 uint8_t * 的大小,对吗? - Glenn
确实。已经更正了。@Glenn - alk
显示剩余2条评论

3
void alloc_img(struct image * img, enum img_type type, unsigned int num_vals){
    size_t basesize = 0;
    switch (type){
        case IMG_U8:
            basesize = sizeof(uint8_t);
        break;            
        case IMG_U16:
            basesize = sizeof(uint16_t);
        break;
        case IMG_FLT:
            basesize = sizeof(float);
        break;
    }
    if (basesize) {
        img->rawdata.u8 = malloc(num_vals * basesize);
        // assigning to u8 is the same as assigning to any other member
        // but it may be better to use a void* as in
        // img->rawdata.voidptr = malloc(num_vals * basesize);
    } else {
        fprintf(stderr, "default missing in switch\n");
    }
}

1
我会在switch语句中添加一个“default”分支来记录/返回错误。 - alk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接