如何在C中存储不同类型的数据?

How to store data of different type in C?

我想存储一些数据,这些数据可以是仅在运行时才知道的不同类型。我不想浪费任何内存,我想将所有数据读取为浮点值。在 C++ 中,我会做这样的事情

struct IA {
  virtual float value(int index) = 0;
};

template<class T>
struct A : public IA {
  A(T* bytes, uint32_t size) { 
    values.resize(size);
    memcpy(values.data(), bytes, size*sizeof(T));
  }
  float value(int index) override {
      return static_cast<float>(values[index]);
  }
  std::vector<T> values;
};


int main() {
  uint16_t bytes[] = {1, 2, 3, 4, 5 }; 
  IA *a = new A<uint16_t>(bytes, 5);
  float value = a->value(0);
  delete a;
}

我的问题是如何使用 C 而不是 C++ 来完成此操作。我可以将数据存储在 uint8_t 数组中,并存储 nr 个位和一个 signed 值。然后每次我从数组中读取一个值时使用 switch case 和 cast。但是如果数组很大并且有很多读取,那将是昂贵的。

我也希望能够传递数组而不必做任何特殊的 if 情况?

你可以用函数指针模拟虚方法:

typedef struct
{
    void* userData;
    float (*value)(void*, int);
} IA;

float get_float_from_uint16(void* userData, int index)
{
    return ((uint16_t*) userData)[index];
}

int main()
{
    uint16_t bytes[] = {1, 2, 3, 4, 5 }; 
    IA a = {bytes, &get_float_from_uint16};

    int index = 0;
    float f = a.value(a.userData, index);
}

Demo

当然,这很简单(剧透警告:不,这并不容易,非常难)。下面的代码我相信和 C++ 一样,但是是用 C 写的。

那么会发生什么:

  • 首先我定义了struct IA_s,这是一个访问类IA对象的接口。该结构仅包含用于访问对象的虚拟 table。 AI 对象有一个访问器 IA_value 与代码中的 IA::value 功能相同。我还将对象析构函数添加到作为析构函数的虚拟 table 和 IA_fini 函数中。它在您的代码中丢失并且您的代码泄漏内存。
  • 然后我创建两个大宏 A_CA_HA_H 旨在用于头文件中。 A_C 旨在用于源文件内部。他们为任何类型定义了 A 对象的通用实现。第一个宏参数是用于为所有导出符号添加前缀的前缀。第二是类型。 A 对象包含一些类型元素的动态数组。
  • 然后我在带有 uint16_ 前缀的 uint16_t 类型上实例化通用宏。
  • 然后在 main 函数中,我为 A 对象分配内存,调用它的构造函数,将其转换为 IA 对象,获取第二个索引的值并调用所有析构函数。

备注:

  • 构造函数命名为 *_init,析构函数命名为 *_fini,所有涉及对象的函数命名为 object_*.
  • 这很有趣
  • 我想我永远不会在生产中使用这样的代码
  • 在 valgrind 中释放所有堆

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* IA object ---------------------------------------------- */

// forward definition for IA__vtable_s
struct IA_s;

// the virtual table of the object
struct IA__vtable_s {
    float (*value)(struct IA_s *ctx, size_t index);
    void (*fini)(struct IA_s *ctx);
};

// The structure, holds only the vtable
struct IA_s {
    const struct IA__vtable_s *vtable;
};

/**
 * Get the value holded in index index
 * @param t
 * @return the value
 */
float IA_value(struct IA_s *t, size_t index) {
    assert(t != NULL);
    assert(t->vtable != NULL);
    assert(t->vtable->value != NULL);
    return t->vtable->value(t, index);
}

/**
 * Call the destructor of IA object
 */
void IA_fini(struct IA_s *t) {
    assert(t != NULL);
    assert(t->vtable != NULL);
    assert(t->vtable->fini != NULL);
    t->vtable->fini(t);
}

/* A object ---------------------------------------------- */

/**
 * Header template of A object
 */
#define A_H(NAME, TYPE)  \
/* A object */ \
struct NAME##A_s { \
\
   /* pointer to allocated array */ \
   TYPE *arr; \
\
   /* elements in the array. */ \
   size_t cnt; \
\
   /* I think we have to have that. */ \
   struct IA_s ia; \
\
}; \
\
/* Initializes A object. The *bytes are copied into the object */ \
/* @param t */ \
/* @patam bytes pointer to size count of objects */ \
/* @param size */ \
/* @returns negative errno value on error, 0 on success */ \
int NAME##A_init(struct NAME##A_s *t, TYPE *bytes, size_t size); \
\
/* Deinitializes A object. */ \
void NAME##A_fini(struct NAME##A_s *t); \
\
/* Converts A object in IA object */ \
struct IA_s *NAME##A_to_IA(struct NAME##A_s *t);
// end of A_H

#define A_C(NAME, TYPE) \
\
/* A to IA interface ----------------------------------------------------- */ \
\
/* Convert pointer to IA_s to pointer to A_s */ \
static \
struct NAME##A_s *NAME##A__pnt_from_ia(struct IA_s *t) { \
    return (void*)( ((unsigned char*)(void *)t) - offsetof(struct NAME##A_s, ia) ); \
} \
\
/* this is our value function inside IA interface */ \
static \
float NAME##A__IA_value(struct IA_s *ia, size_t index) { \
    struct NAME##A_s *t = NAME##A__pnt_from_ia(ia); \
    assert(t != NULL); \
    /* UB happens, as in case of vector::operator[] */ \
    assert(index <= t->cnt); \
    return t->arr[index]; \
} \
\
/* this is our destructor inside IA interface */ \
static \
void NAME##A__IA_fini(struct IA_s *ia) { \
    struct NAME##A_s *t = NAME##A__pnt_from_ia(ia); \
    assert(t != NULL); \
    NAME##A_fini(t); \
} \
\
static const struct IA__vtable_s NAME##A__IA_vtable = { \
    .value = NAME##A__IA_value, \
    .fini = NAME##A__IA_fini, \
}; \
\
int NAME##A_init(struct NAME##A_s *t, TYPE *bytes, size_t cnt) { \
    assert(t != NULL); \
    assert(bytes != NULL); \
    if (cnt == 0) {  \
       /* malloc(0) happens to be nonportable */ \
       t->arr = NULL; \
       t->cnt = 0; \
       return 0; \
    } \
    if (SIZE_MAX / sizeof(*t->arr) < cnt) { \
    /* multiplication overflow */ \
    return -EOVERFLOW; \
    } \
    t->arr = malloc(cnt * sizeof(*t->arr)); \
    if (t->arr == NULL) { \
        return -ENOMEM; \
    } \
    t->cnt = cnt; \
    memcpy(t->arr, bytes, cnt * sizeof(*t->arr)); \
    t->ia.vtable = &NAME##A__IA_vtable; \
    return 0; \
} \
void NAME##A_fini(struct NAME##A_s *t) { \
    assert(t != NULL); \
    free(t->arr); \
    t->arr = NULL; \
    t->cnt = 0; \
} \
struct IA_s *NAME##A_to_IA(struct NAME##A_s *t) { \
     assert(t != NULL); \
     return &t->ia; \
}

// A<uint16_t>
A_H(uint16_, uint16_t)
A_C(uint16_, uint16_t)

int main() {
    uint16_t bytes[] = {1, 2, 3, 4, 5 };

    // new()
    struct uint16_A_s *temp = malloc(sizeof(*temp));
    if (temp == NULL) {
        // destructors
        abort();
    }

    // A::A(bytes, 5)
    int ret = uint16_A_init(temp, bytes, 5);
    if (ret != 0) {
        // destructors
        free(temp);
        abort();
    }

    // IA *ia = A;
    struct IA_s *ia = uint16_A_to_IA(temp);
    if (ia == NULL) {
        // destructors
        uint16_A_fini(temp);
        free(temp);
        abort();
    }

    // finally call IA::value(2)
    float value = IA_value(ia, 2);
    printf("%f\n", value);

    // destructors
    IA_fini(ia); // or uint16_A_fini(temp) depending on which you call delete on

    free(temp);
}