c中的通用数据类型

Generic datatype in c

我正在用 C 开发一个小型图像处理库。

我想将图像表示为可以具有不同类型的像素数组:uint8_t(对于最大值不超过 255 的像素),uint16_t(相同但最大值为 65535), uint32_t..

我暂时要这样做:

typedef struct img8_t {
    unsigned int width, height, depth;
    size_t dsize;
    uint8_t *data;
}img8_t;

typedef struct img16_t {
    unsigned int width, height, depth;
    size_t dsize;
   uint16_t *data;
}img16_t;

typedef struct img32_t {
    unsigned int width, height, depth;
    size_t dsize;
    uint32_t *data;
}img32_t;

dsize 包含像素数据类型的大小

而且我有与 allocate/deallocate 相同数量的功能来处理这些图像。

有没有一种方法可以定义一个通用 "unsigned int" 类型来处理 8 位、16 位等值,而无需为每种情况创建 struct/function?

我应该使用联合吗?

感谢您的帮助!

您可以使用 void * 作为通用指针:

typedef struct img_t {
    unsigned int width, height, depth;
    size_t dsize;
    void *data;
} img_t;

然后使用dsize成员来计算如何转换它:

int value;
switch (my_img_t.dsize) {
case 8:
    value = ((uint8_t *)my_img_t.data)[idx];
    break;
case 16:
    value = ((uint16_t *)my_img_t.data)[idx];
    break;
case 32:
    value = ((uint32_t *)my_img_t.data)[idx];
    break;
}

如果在 C 中完成,这将至少需要不同的语句来处理不同的大小,如:

void handle_img(void * ptr, unsigned size)
{
if(sizeof(uint8-_t)==sze)
{...}
else if(sizeof(uint16_t)==size)
{...}
else if(sizeof(uint32_t)==size)
{}
}

根据具体的操作,将各个处理程序分解为单独的函数可能是有意义的。关键是,与 C++ 不同,在某些时候您将不得不为每个分支单独编写代码。

Is there a way to define a generic "unsigned int" type that can handle values on 8-bits, 16-bits etc.. without creating a struct/function for each case ?

不,这不适合您的目的。每个完整的 C 数据类型都有一个特定的表示,只有当你有一个完整的类型时,你才能读取或写入对象的值。您当然可以定义一个可以容纳不同大小整数的联合:

union uint_any {
    uint8_t  u8;
    uint16_t u16;
    uint32_t u32;
};

...但是该类型的对象都具有相同的大小,足以容纳最大的成员。因此,一个光栅,比如说,没有任何填充的 16 位像素与 union uint_any 的数组不匹配,至少如果您想通过 u16 成员读取所有像素,则不会。


可以使用void *指向您的数组,以避免每个像素大小的单独结构,并且您的函数可以在内部对其进行排序,因此每个目的你只需要一个功能。此外,如果您有足够复杂的图像处理函数来保证它,那么您可能会受益于使用宏来减少或消除代码重复。这是一个比可能需要这样处理的更简单的例子:

struct img {
    unsigned int width, height, depth;
    size_t dsize;
    void *data;
};

uint32_t get_pixel(struct img *image, unsigned x, unsigned y) {
    // Warning: evaluates its first argument twice:
    #define GET_PIXEL(i,t,x,y) (((t*)((i)->data))[(x)*((i)->width)+(y)])
    switch (img.dsize) {
        case 8:
            return GET_PIXEL(image, uint8_t, x, y);
        case 16:
            return GET_PIXEL(image, uint16_t, x, y);
        case 32:
            return GET_PIXEL(image, uint32_t, x, y);
        default:
            // handle error ...
    }
    #undef GET_PIXEL
}

Is there a way to define a generic unsigned int type that can handle values on 8-bits, 16-bits etc.. without creating a struct/function for each case ?

为避免转换,请考虑 union 个指针。

typedef struct imgN_t {
    unsigned int width, height, depth;
    size_t dsize;
    union {
      uint32_t *u32;
      uint16_t *u16;
      uint8_t *u8;
    } data;
} imgN_t;

通用函数:

// return widest
uint32_t img_get_pixel(imgN_t *i, unsigned x, unsigned y) {
  switch (i->depth) {
    case 8: return i->data.u8[y*i->width + x];
    ...  // other sizes
    case 32: return i->data.u32[y*i->width + x];
  }
}

或每个都具有更高的性能。

uint8_t img_get_pixel8(imgN_t *i, unsigned x, unsigned y) {
  assert(i->depth == 8);
  return i->data.u8[y*i->width + x];
}

uint16_t img_get_pixel16(imgN_t *i, unsigned x, unsigned y) {
  assert(i->depth == 16);
  return i->data.u16[y*i->width + x];
}

uint32_t img_get_pixel32(imgN_t *i, unsigned x, unsigned y) {
  assert(i->depth == 32);
  return i->data.u32[y*i->width + x];
}

根据项目的大小和安全要求,您还可以使用 C 宏来生成通用代码。

请记住,这会变得非常丑陋非常快,但它会为您提供类型检查,例如将 1024 分配给 img8_t 会在编译时给您警告,而不仅仅是 运行 时的静默溢出。而且您还一次实施所有内容。

示例实现:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

// declarations

#define UINT_T(SIZE) uint ## SIZE ## _t
#define IMG_T(SIZE) img ## SIZE ## _t

#define DECLARE_IMG(SIZE) \
typedef struct IMG_T(SIZE) \
{ \
    unsigned int width, height, depth; \
    size_t dsize; \
    UINT_T(SIZE) *data; \
} IMG_T(SIZE); \
\
UINT_T(SIZE) img ## SIZE ## _get_pixel(IMG_T(SIZE) *img, size_t index) \
{ \
    return img->data[index]; \
} \
\
void img ## SIZE ## _set_pixel(IMG_T(SIZE) *img, size_t index, UINT_T(SIZE) value) \
{ \
    img->data[index] = value; \
}

DECLARE_IMG(8)
DECLARE_IMG(16)

// usage

int main(void)
{
    uint8_t data[1];
    img8_t img8 = { .data = data };

    img8_set_pixel(&img8, 0, 1024U);

    printf("%u\n", img8_get_pixel(&img8, 0));

    return 0;
}

这不会让您在维护期间度过愉快的时光,但如果您正在编写非常通用的库代码,那么这可能是值得的。

您还可以创建自定义初始化函数,将 dsize 设置为适当的值(例如 UINT ## SIZE ##_MAX)。

这个答案的概要:

  • 类型特定 CPU 指令编码。
  • 中间代码及其在 GPGPU 中的使用。

作为一种“高级汇编”语言,C 没有也不能提供算术泛型类型——C 中的每个算术运算都有明确的类型。

这是因为算术运算在目标代码中编译为不同的 CPU 指令。很少 CPU 对大小由某种“标志”确定的数据进行操作。

另一方面,您似乎有一个多媒体应用程序,具体来说,是一个图像处理库。在多媒体应用程序中,以及在 3D 和图形密集型应用程序中,通常使用 OpenGL 和 Vulkan 等 API 来加速图像处理。

在这些图形 API 中,有“着色器”的概念,它是 GPU 上的应用程序 运行。这些程序现在是从中间代码编译而来的——SPIR-V 和 LLVM-IR 就是这样的例子。

如果您能够承受在计算机某处执行之前将元代码编译成中间代码的复杂性,则可以在您的应用程序中集成一个代码生成库。 https://llvm.org在这方面是很有名的。