【问题标题】:What is the proper way to allocate memory in C for different data types?在 C 中为不同数据类型分配内存的正确方法是什么?
【发布时间】:2020-10-01 19:10:41
【问题描述】:

我正在将图像读取到动态分配的数组中,图像像素类型可以是以下任何一种类型:

typedef enum {
              Byte = 1,//unsigned char
              Int32 = 2,//int
              Float32 = 3,//float
              Float64 = 4//double 
}DataType;

我正在考虑使用switch 块在void 指针上分配内存:

int NumPix = GetImageSize(Image);
DataType theDataType  = GetImageDataType(Image);
void *data;
switch (theDataType)
{
    case Byte:
      data = (unsigned char *)malloc(NumPix * sizeof(unsigned char));
      break;
    case Int32:
     data = (int *)malloc(NumPix * sizeof(int));
     break;
    case Float32:
     data = (float *)malloc(NumPix * sizeof(float));
     break;
    case Float64 :
     data = (double*)malloc(NumPix * sizeof(double));
     break; 


}
// do something with data
free(data);

从合法的意义上说,这种方式可以吗?有没有其他方法可以用更少的代码和更通用的方法来做到这一点?

【问题讨论】:

  • 可能感兴趣的是Do I cast the result of malloc?,尤其是在这里,因为malloc() 无论如何都会返回void*,而目标是void *data,以后你制作的演员表就没有记忆了。
  • 从技术上讲,int 不必是 32 位。您可以检查您的实现中是否存在可选的fixed-width integer types(很可能)并使用它们。
  • 我认为您的代码没有其他问题。但是,我怀疑创建一个函数来处理所有 4 种情况是否有意义,因为您经常需要检查 theDataType 并有条件地调用代码。因此,我相信拥有 4 个单独的函数会更有意义,每个函数都专门针对一种数据类型,然后有条件地调用该函数。这样,您只有一个分支而不是多个分支。 Branches are expensive in terms of performance.
  • 请注意,在 C++ 中,为了为每种数据类型创建 4 个专用函数,您可以简单地编写 1 个带有模板参数的函数。然后编译器会自动为您创建 4 个函数,一个用于每种可能的数据类型。但是,由于您标记了问题 C 而不是 C++,因此我不会进一步讨论这种可能性(除非您问)。
  • 大概在某个地方你也有union { uchar *u; int *i; float *f; double *d } t(或类似的)。所以你可以做switch(theDataType) { case Byte: t.u = malloc( NumPix * sizeof *t.u); ....

标签: c memory memory-management


【解决方案1】:

您将需要知道许多地方的像素大小。所以定义一个函数来计算像素大小并在每次需要时使用它。

size_t pixel_size(DataType type) {
    switch (type) {
    case Byte: return sizeof(unsigned char);
    case Int32: return sizeof(unsigned int); // Shouldn't this be uint32_t?
    case Float32: return sizeof(float);
    case Float64: return sizeof(double);
    }
}

// Allocate an image with indeterminate content.
// Return NULL if the allocation fails.
void *allocate_indeterminate_image(DataType type, size_t x, size_t y) {
    size_t pixel_count = x * y; // overflow checking omitted
    size_t byte_count = pixel_count * pixel_size(type); // overflow checking omitted;
    return malloc(byte_count);
}

// Allocate an image with all-bits-zero. On almost every platform,
// this means that each pixel has the value 0 (+0.0 for floats).
// Return NULL if the allocation fails.
void *allocate_blank_image(DataType type, size_t x, size_t y) {
    size_t pixel_count = x * y; // overflow checking omitted
    size_t bytes_per_pixel = pixel_size(type);
    return calloc(pixel_count, bytes_per_pixel);
}

【讨论】:

    【解决方案2】:

    我稍微更改了您的程序,以一种我在这里用于其他事情的方式,我将把它留在这里作为对您的案例有用的示例,以及测试数据

    图像数据结构

    typedef struct
    {
        uint32_t    _limit; 
        uint32_t    _imgC; // argc
        Record**    _imgV; // argv
    
    }   ImageData;
    
    • ImageData 这里是一个动态结构体,容量为_limitRecord 数据结构体。
    • imgC是实际使用的记录数
    • Record** 是指向 Record 结构的指针数组的指针,该结构的长度为 1、2、4 或 8 字节像素数据。这样它就可以同时保存任意数量的任意大小的像素数据
    • 这与main() 中的argc/argv 对完全相同

    Record 结构

    typedef void Data;
    
    typedef struct
    {
        uint8_t     _id; // 1,2,4 or 8
        uint32_t    _NumPix; // argc
        Data*       _data; // argv
    
    }   Record;     // one record of image data
    
    • _id 是像素数据长度
    • _NumPix 是像素的总数,正如您所介绍的那样
    • 这里的东西和你的程序不同:像素数据在这里分配,是一个_id * _NumPix字节的区域,像素数据按顺序排列,就像在我的位图构建程序中我改用你的数据一样; )
    • 像素数据在此处以_id 组的形式打包并根据需要解包,只使用void* 指针。

    例子

    我使用了随机数据和一个“工厂”函数,它构建并返回随机 1-2-4 或 8 字节图像

    像素工厂函数

    Record* getImage()
    {
    
        const uint8_t len[4] = { 1,2,4,8 };
        Record* one = (Record*)malloc(sizeof(Record));
        one->_id = len[rand() % 4];
        one->_NumPix = 1 + rand() % 8;
        // now builds the pixels with random values under 255
        // here the actual size of EACH record is computed
        uint32_t total = one->_NumPix * one->_id; 
        void* p = (Data**)malloc(total);
        uint8_t value = rand() % 255;
        // here you get the actual pixel data
        memset(p, value, total); 
        one->_data = (Data**)p;
        return one;
    };
    

    程序逻辑

    • 开始时的 3 个常量定义了运行:
    #define     _BLOCK_SIZE_ 50
    #define     _IMAGES_     5
    #define     _SEED_       201001
    
    • 图像内存分配在_BLOCK_SIZE_Record*指针块中。在数据采集阶段结束时,阵列被修剪到确切使用的大小。

      • _IMAGES_ 是要构建的图像数量,所有图像都会在 ImageData 数组构建时显示。

      • _SEED 是随机数据的种子

    • 创建数组时,最终未使用的数据为free(),因此ImageData 仅包含与图像数据对应的记录。

    • 程序然后显示一些随机记录,这次形成ImageData 数组,以便我们可以将其与构建阶段显示的数据进行比较。

    输出示例

    DumpImage(Sample 0): 8 8-pixels images [64 bytes]:
    
         0: EC EC EC EC EC EC EC EC
         1: EC EC EC EC EC EC EC EC
         2: EC EC EC EC EC EC EC EC
         3: EC EC EC EC EC EC EC EC
         4: EC EC EC EC EC EC EC EC
         5: EC EC EC EC EC EC EC EC
         6: EC EC EC EC EC EC EC EC
         7: EC EC EC EC EC EC EC EC
    
    DumpImage(Sample 1): 4 2-pixels images [8 bytes]:
    
         0: 02 02
         1: 02 02
         2: 02 02
         3: 02 02
    
    DumpImage(Sample 2): 3 2-pixels images [6 bytes]:
    
         0: DC DC
         1: DC DC
         2: DC DC
    
    DumpImage(Sample 3): 5 2-pixels images [10 bytes]:
    
         0: 75 75
         1: 75 75
         2: 75 75
         3: 75 75
         4: 75 75
    
    DumpImage(Sample 4): 7 4-pixels images [28 bytes]:
    
         0: 54 54 54 54
         1: 54 54 54 54
         2: 54 54 54 54
         3: 54 54 54 54
         4: 54 54 54 54
         5: 54 54 54 54
         6: 54 54 54 54
    
    
    
    5 Test images loaded
    now shows 2 random images from list
    
    
    DumpImage(From Array: image 3): 5 2-pixels images [10 bytes]:
    
         0: 75 75
         1: 75 75
         2: 75 75
         3: 75 75
         4: 75 75
    
    DumpImage(From Array: image 0): 8 8-pixels images [64 bytes]:
    
         0: EC EC EC EC EC EC EC EC
         1: EC EC EC EC EC EC EC EC
         2: EC EC EC EC EC EC EC EC
         3: EC EC EC EC EC EC EC EC
         4: EC EC EC EC EC EC EC EC
         5: EC EC EC EC EC EC EC EC
         6: EC EC EC EC EC EC EC EC
         7: EC EC EC EC EC EC EC EC
    
            50 pointers allocated
            5 arguments read
            45 pointers to free
            Block size trimmed for a total of 5 pointers
            Image array is ready for use
    

    示例程序到此结束。我没有移植内存的释放,这是微不足道的,并且没有使用数据写任何内容,因为我对此一无所知:)

    我也没有做太多测试。请不要在此处进行宗教讨论:是的,我在结构字段的开头使用了下划线。我只使用了现在在这里运行的 Microsoft 编译器 CL 16.7.4。

    我将此处的数字设置为 5 条记录,以便发布所有输出数据。但是由于内存中只包含数据,而元数据只有几个词,因此可以运行数千条记录。而且速度很快。

    对于大型数据集,最好将 BLOCK_SIZE 更改为更大的数字

    示例代码

    #define     _BLOCK_SIZE_ 50
    #define     _IMAGES_     5
    #define     _SEED_       201001
    
    #include <math.h>
    #include <memory.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef void Data;
    
    typedef struct
    {
        uint8_t     _id; // 1,2,4 or 8
        uint32_t    _NumPix; // argc
        Data*       _data; // argv
    
    }   Record;     // one record of image data
    
    typedef struct
    {
        uint32_t    _limit; 
        uint32_t    _imgC; // argc
        Record**    _imgV; // argv
    
    }   ImageData;
    
    int         dumpImage(const char*,Record*);
    Record*     getImage();
    
    
    int main(void)
    {
        srand(_SEED_);
        char message[30];
        ImageData   image;
        image._imgC = 0;
        image._limit = _BLOCK_SIZE_;
        image._imgV = (Record**)malloc( image._limit * sizeof(Record*) );
    
        Record*      sandbox;
        for (int i = 0; i < _IMAGES_; i += 1)
        {
            // gets an image and a Record to hold it
            sprintf(message, "Sample %d", i);
            sandbox = getImage(); // gets a random image
            dumpImage(message, sandbox);
    
    
            Record* target = (Record*)malloc(sizeof(Record)); // new one
            target->_id = sandbox->_id;
            target->_NumPix = sandbox->_NumPix;
            uint32_t total = sandbox->_NumPix * sandbox->_id;
            target->_data = (void*)malloc(total);
    
            memcpy(target->_data, sandbox->_data, total);
            image._imgV[image._imgC] = target;
    
            // expands the block if there is no space left
            if (image._imgC >= image._limit)
            {   // block is full
                image._limit += _BLOCK_SIZE_;
                char* new_block = realloc(image._imgV, (image._limit * sizeof(char*)));
                printf("Block extended for a total of %d pointers\n", image._limit);
                image._imgV = (Record**)new_block;
            };  // if()
    
            image._imgC += 1;
    
        };  // for
    
        printf("\n\n%d Test images loaded\n", image._imgC);
        printf("now shows %d random images from list\n\n\n", _IMAGES_ / 2 );
    
        for (int i = 0; i < _IMAGES_ / 2; i += 1)
        {
            int j = rand() % _IMAGES_;
            sprintf(message, "From Array: image %d", j);
            sandbox = getImage(); // gets a random image
            dumpImage(message,image._imgV[j]);
        };  // for()
    
    
        // now trims the end of the block
        // allocated: _limit
        // used: argc
        printf("\t%d pointers allocated\n", image._limit);
        printf("\t%d arguments read\n", image._imgC);
        if (image._limit == image._imgC)
            printf("\tNothing to free()\n");
        else
        {
            printf("\t%d pointers to free\n", image._limit - image._imgC);
            char* new_size = realloc(image._imgV, (image._imgC * sizeof(char*)));
            printf("\tBlock size trimmed for a total of %d pointers\n", image._imgC);
            image._imgV = (Record**)new_size;
        };
    
        printf("\tImage array is ready for use\n");
    
        return 0;
    };  // main()
    
    
    int         dumpImage(const char* msg, Record* img)
    {
        /*
            uint8_t     _id; // len
            uint32_t    _NumPix; // argc
            Data**      _data; // argv
         */
        uint32_t total = img->_NumPix * img->_id;
        uint8_t* p = (void*)img->_data;
        printf("DumpImage(%s): %d %d-pixels images [%d bytes]:\n\n", msg, img->_NumPix, img->_id, total);
        for (uint32_t i = 0; i < img->_NumPix; i += 1)
        {
            printf("%6d: ", i);
            for (int j = 0; j < img->_id; j++)
                printf("%02X ", *p);
            printf("\n");
        };  // for()
        printf("\n");
        return total;
    };
    
    //
    // Image factory: build random "images"
    // id is 1 to 4, NumPix is 1 to 100, 
    // data is NumPix Pixels with id bytes
    //
    /*
        uint8_t     _id; // len
        uint32_t    _NumPix; // argc
        Data**      _data; // argv
     */
    Record* getImage()
    {
    
        const uint8_t len[4] = { 1,2,4,8 };
        Record* one = (Record*)malloc(sizeof(Record));
        one->_id = len[rand() % 4];
        one->_NumPix = 1 + rand() % 8;
        // now builds the pixels with random values under 255
        uint32_t total = one->_NumPix * one->_id;
        void* p = (Data**)malloc(total);
        uint8_t value = rand() % 255;
        memset(p, value, total);
        one->_data = (Data**)p;
        return one;
    };
    

    【讨论】:

      【解决方案3】:

      从合法的意义上说,这种方式可以吗?

      当然。顺便提一句。您可以仅抽象出大小,并拥有一个根据数据类型仅返回sizeof 的函数,然后执行malloc(NumPix * data_get_size_of_object(DataType))。 Real world example - a big function that returns sizeof(type) in a big switch.

      有没有其他方法可以用更少的代码和更通用的方法来做到这一点?

      嗯,不是“更少的代码”。

      你最终会得到很多我称之为“fat 开关”的东西。您将拥有的每个功能都将是一个很大的switch(type) { case sometype: do_something_with_that_type((cast_to_type*)pointer); case someothertype: etc. },它将变成 1000 行长的无法阅读的开关混乱。它不会很灵活。添加新功能会很慢。维护会很慢。因为您必须每次都考虑每个案例,所以会出现错误。换个思路——不是函数应该知道如何处理类型,type 本身应该知道操作。因此,术语面向对象编程。创建一个 interface 并创建实现该接口的 objects - 这样对象就会跟踪发生在它们身上的事情。 Real life example - struct file_operations from linux kernel.

      我最近的经历让我相信有一些与函数指针相关的恐惧。函数指针可用于选择一次要做什么,然后根据先前的选择执行不同的操作。分配函数指针的一个开关,然后只需一次跳转到先前分配的函数。无需一次又一次地选择。一些带有虚拟表和界面的设计可能如下所示:

      #include <stdio.h>
      #include <stdlib.h>
      #include <stddef.h>
      #include <errno.h>
      
      typedef struct dataif_s dataif_t;
      
      struct dataif_vtable_s {
          void (*free)(dataif_t *t); // destructor
          int (*read_data)(dataif_t *t, FILE *from); // read data from file
          int (*save)(dataif_t *t, FILE *to); // serialize data to file
          int (*fprint)(dataif_t *t, FILE *to); // print in human readable format
          int (*add)(dataif_t *t, int a); // add integer to each number
      };
      
      struct dataif_s {
          // a pointer to virtual table that implements operations
          const struct dataif_vtable_s *vtable;
          // a pointer to provide object's data
          void *priv;
      };
      
      // shortcut accessors
      int dataif_read_data(dataif_t *t, FILE *from) {
           // add assert(t != NULL); etc. to ease bugs detection
           // or maybe handle 'if(t->vtable->read_data != NULL) return -ENOSYS;' ?
           return t->vtable->read_data(t, from);
      }
      
      int dataif_print(dataif_t *t) {
          return t->vtable->fprint(t, stdout);
      }
      
      void dataif_free(dataif_t *t) {
           t->vtable->free(t);
      }
      // etc. for each function
      
      /* ------------------------------------------ */
      
      // private data implementing unsigned char operations
      struct bytedata_s {
          size_t count;
          unsigned char *data;
      };
      
      int bytedata_read_data(dataif_t *t, FILE *from);
      void bytedata_free(dataif_t *t);
      
      // the virutal table
      // note it's static const - it's stored in .rodata, saves RAM memory
      static const struct dataif_vtable_s bytedata_vtable = {
           .free = bytedata_free,
           .read_data = bytedata_read_data,
           /* etc. fill with custom function pointers */
      };
      
      // Constructor for data of unsigned chars
      int bytedata_init(dataif_t *t, size_t NumPix) {
           // construct bytedata object
           struct bytedata_s *p = malloc(sizeof(*p));
           if (!p) goto ERR_p;
           p->data = malloc(NumPix * sizeof(*p->data));
           if (!p->data) goto ERR_data;
           p->count = NumPix;
      
           // create the interface
           t->priv = p;
           t->vtable = &bytedata_vtable;
      
           return 0;
           free(p->data);
           ERR_data:
           free(p);
           ERR_p:
           return -ENOMEM; 
      }
      
      // reading values
      int bytedata_read_data(dataif_t *t, FILE *from) {
          struct bytedata_s *p = t->priv; // extract out object
          for (size_t i = 0; i < p->count; ++i) {
              if (fscanf(from, "%hhu", &p->data[i]) != 1) return -1;
          }
          return 0;
      }
      
      void bytedata_free(dataif_t *t) {
           // free data allocated in constructor
           struct bytedata_s *p = t->priv;
           free(p->data);
           free(t->priv);
      }
      
      /* ---------------------------------------------- */
      
      // create same interfaces for each type
      // note - only one single function is visible externally
      // all other functions are accessible via virtual table
      int int32data_init(dataif_t *t, size_t NumPix);
      int float32data_init(dataif_t *t, size_t NumPix);
      int float64data_init(dataif_t *t, size_t NumPix);
      
      /* ---------------------------------------------- */
      
      // map value types to constructors, ie. "object factory"
      typedef enum {
          DATATYPE_BYTE,     //unsigned char
          DATATYPE_INT32,    //int
          DATATYPE_FLOAT32,  //float
          DATATYPE_FLOAT64,  //double 
      } datatype_t; // I will not use UpperCamelCase
      
      // map of datatypes to constructors
      static const int (*dataif_inits[])(dataif_t *t, size_t NumPix) = {
          [DATATYPE_BYTE] = bytedata_init,
          [DATATYPE_INT32] = int32data_init,
          [DATATYPE_FLOAT32] = float32data_init,
          [DATATYPE_float64] = float64data_init,
      };
      
      int dataif_init(dataif_t *t, datatype_t datatype, size_t NumPix) {
          if (datatype < 0 || datatype > sizeof(dataif_inits)/sizeof(*dataif_inits)) {
              // the datatype not found in the array
              return -EINVAL;
          }
          // note how the switch... doesn't exists anymore.
          return dataif_inits[datatype](t, NumPix);
      }
      
      # if 0
      // if enum values doesn't start from zero
      // use a structure and a for to find the mapping between an enum to constructor
      struct datatype_to_init_s {
           datatype_t t;
           int (*init)(dataif_t *t, size_t NumPix);
      };
      static const struct datatype_to_init_s datatype_to_init[] = {
           { DATATYPE_BYTE, bytedata_init },
           /* etc. */
      };
      int dataif_init(dataif_t *t, datatype_t datatype, size_t NumPix) {
          // find datatype in datatype_to_init
          for (size_t i = 0; i < sizeof(datatype_to_init)/sizeof(*datatype_to_init); ++i) {
               if (datatype_to_init[i].t == datatype) {
                   // found it? create the object
                   return datatype_to_init[i].init(NumPix);
               }
          }
          // the datatype not found in the array
          return -EINVAL;
      }
      # endif
      
      /* ---------------------------------------------- */
      
      size_t image_get_size(const char *);
      datatype_t image_get_datatype(const char *);
      static const char *image = "/tmp/a.png";
      
      int main() {
           int err = 0;
           size_t numpix = image_get_size(image);
           datatype_t thedatatype  = image_get_datatype(image);
           FILE *some_file = fopen(image, "r");
           if (some_file == NULL) { err = -1; goto ERR_fopen; }
      
           dataif_t data;
           err = dataif_init(&data, thedatatype, numpix); // initializes data depending on datatype
           if (err) goto ERR_dataif_init; // add a friendly error message
      
           err = dataif_read_data(&data, some_file);
           if (err) goto ERR_read_data; 
      
           dataif_do_something(&data);
           dataif_print(&data);
      
           ERR_read_data:
           dataif_free(&data);
           ERR_dataif_init:
           fclose(some_file);
           ERR_fopen:
           return err;
      }
      

      【讨论】:

        猜你喜欢
        • 2015-10-04
        • 2011-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-10
        • 2013-08-30
        • 1970-01-01
        • 2011-08-29
        相关资源
        最近更新 更多