【问题标题】:Generic datatype in cc中的通用数据类型
【发布时间】:2018-05-26 16:14:42
【问题描述】:

我正在使用 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 包含像素数据类型的大小

而且我有相同数量的函数来分配/解除分配和处理这些图像。

有没有办法定义一个通用的“unsigned int”类型,可以处理 8 位、16 位等的值,而无需为每种情况创建结构/函数?

我应该使用联合吗?

感谢您的帮助!

【问题讨论】:

  • 你有一个指向数据的指针。使用通用指针类型:void *。而且...您始终可以对其中的每一个使用uint32_t,这只会浪费一些内存:D
  • 感谢您的快速回答!是的,我尝试了 void * 解决方案,但如果我想访问给定索引处的像素,我仍然需要一个数据类型来转换数据。正如你所说,我也可以使用 uint32_t,但我想根据像素最大值使用适当的数据类型。再次感谢!
  • 关于图像格式:width, height, depth的范围通常由格式指定。我建议这些成员也使用uintN_t

标签: c generics unions


【解决方案1】:

有没有一种方法可以定义一个通用的“unsigned int”类型,它可以处理 8 位、16 位等的值,而无需为每种情况创建一个结构/函数?

不,这不适合您的目的。每个完整的 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
}

【讨论】:

    【解决方案2】:

    您可以使用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;
    }
    

    【讨论】:

      【解决方案3】:

      如果在 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++ 不同的是,在某些时候您将不得不为每个分支单独编写代码。

      【讨论】:

        【解决方案4】:

        有没有办法定义一个通用的 unsigned int 类型,它可以处理 8 位、16 位等的值,而无需为每种情况创建结构/函数?

        为避免强制转换,请考虑使用 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];
        }
        

        【讨论】:

          【解决方案5】:

          根据您项目的大小和安全要求,您还可以使用 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)。

          【讨论】:

            【解决方案6】:

            这个答案的概要:

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

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

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

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

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

            如果您能够承受在计算机上某处执行元代码之前将其编译为中间代码的复杂性,您可以在您的应用程序中集成代码生成库。 https://llvm.org是这个领域的知名人士。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-03-03
              • 1970-01-01
              • 1970-01-01
              • 2012-08-09
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多