如何在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);
}
当然,这很简单(剧透警告:不,这并不容易,非常难)。下面的代码我相信和 C++ 一样,但是是用 C 写的。
那么会发生什么:
- 首先我定义了
struct IA_s
,这是一个访问类IA对象的接口。该结构仅包含用于访问对象的虚拟 table。 AI
对象有一个访问器 IA_value
与代码中的 IA::value
功能相同。我还将对象析构函数添加到作为析构函数的虚拟 table 和 IA_fini
函数中。它在您的代码中丢失并且您的代码泄漏内存。
- 然后我创建两个大宏
A_C
和 A_H
。 A_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);
}
我想存储一些数据,这些数据可以是仅在运行时才知道的不同类型。我不想浪费任何内存,我想将所有数据读取为浮点值。在 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);
}
当然,这很简单(剧透警告:不,这并不容易,非常难)。下面的代码我相信和 C++ 一样,但是是用 C 写的。
那么会发生什么:
- 首先我定义了
struct IA_s
,这是一个访问类IA对象的接口。该结构仅包含用于访问对象的虚拟 table。AI
对象有一个访问器IA_value
与代码中的IA::value
功能相同。我还将对象析构函数添加到作为析构函数的虚拟 table 和IA_fini
函数中。它在您的代码中丢失并且您的代码泄漏内存。 - 然后我创建两个大宏
A_C
和A_H
。A_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);
}