想知道是否可以执行以下操作将任意对象存储在C中的数组中:
void *arr[123];
int len = 0;
void
pusharr(void *object) {
arr[len++] = &object;
}
int
main() {
char *foo = "foo"
pusharr(1)
pusharr("foo")
pusharr(&foo)
pusharr(foo)
pusharr(somestruct)
pusharr(someotherstructtype)
pusharr(afunction)
pusharr(anythingbasically)
pusharr(true)
pusharr(NULL)
// arr[4] == somestruct, etc.
}
基本上,我正在尝试像free(void *ptr)
函数那样建模,并将指向任何可能对象类型的通用指针传递到函数中,以便它可以保存对它们的引用。想知道这是否可能,如果不能,那么如何。
就功能而言,就像这样...
因此,this显示了如何传入void指针以从函数中获取任意类型。
void foo(char* szType, void *pOut) {
switch (szType[0]) {
case 'I': *(int*)pOut = 1; break;
case 'F': *(float*)pOut = 1; break;
}
}
int a;
float b;
foo("I", &a);
foo("F", &b);
我想知道是否有办法将其附加到对象/结构上。
struct mydataobject {
void *value;
}
这样,您至少可以让函数返回一个类型。
mydataobject
foo() {
}
就我而言,我想拥有两个可以处理任意数据的函数push
和pop
。
void
mypush(mydataobject something) {
arr[index++] = something
}
mydataobject
mypop() {
return arr[index--]
}
mydataobject a = { "foo" }
mydataobject b = { 123 }
mydataobject c = { true }
mydataobject d = { a }
// it should work with arbitrary data.
想知道是否有这种可能。
答案 0 :(得分:3)
是的,这是可能的。正确地做非常困难。
考虑脚本语言,例如Perl,Python或Javascript。每个变量都使用可以容纳不同类型值的变量。每种脚本语言都是用C编写的。
那他们怎么做呢?
通常,它们使用并集和类型标记。类型标记通常是一个整数,就像您用作szType
一样。有时它们是带有有关类型数据的结构的指针。有时这是一个组合,因为整数标记都在0x1000以下(例如),因此任何较大的数字都必须是指针。
因此,设计一个C联合可以容纳有关您所有数据类型的数据。包括一个指针,这样,超大类型不必使每个类型都变得巨大。然后设计一个包含类型标记和您的并集的结构。
然后针对您创建的用于操纵这些结构的每个函数,检查类型标记并针对每个函数进行正确的操作。
我很无聊。这是一些代码。请注意,这是C99,因此它将无法在旧版本的Visual Studio中编译(VS 2017正常!)。我用gcc和clang编译它。经过valgrind测试,因此没有内存泄漏。用
构建之后gcc -Wall -Wextra -Werror -pedantic -g -O0 type-union-test.c -o type-union-test
使用
运行./type-union-test 11 bb 22333333 dd 10 a 11 b
还有type-union-test.c:的代码(也可以从https://github.com/zlynx/type-union-test获得)
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// log10(2^64) + 2
#define MAX_INTSTRING_LEN 21
enum VAL_types {
VAL_UNDEFINED,
VAL_INT32,
VAL_STRING,
VAL_OBJECT,
};
enum OPS_RESULT_errors {
OPS_RESULT_OK,
OPS_RESULT_FALSE,
OPS_RESULT_UNIMPLEMENTED,
OPS_RESULT_INVALID_TYPE,
OPS_RESULT_INVALID_INTEGER,
};
struct VAL;
struct OBJECT;
union VAL_type_data {
int32_t int32;
char *string;
struct OBJECT *object;
};
typedef struct OPS_RESULT {
struct VAL *val;
enum OPS_RESULT_errors error;
} OPS_RESULT;
typedef struct VAL_OPS {
OPS_RESULT (*set_type)(struct VAL *, enum VAL_types);
OPS_RESULT (*copy_from_int32)(struct VAL *, int32_t);
OPS_RESULT (*copy_from_string)(struct VAL *, const char *);
OPS_RESULT (*move_from_key_val)(struct VAL *, struct VAL *, struct VAL *);
OPS_RESULT (*is_equal)(struct VAL *, struct VAL *);
OPS_RESULT (*debug_print)(struct VAL *);
} VAL_OPS;
typedef struct VAL {
enum VAL_types type_id;
size_t ref_count;
union VAL_type_data type_data;
const VAL_OPS *ops;
bool constant;
bool owned_ptr;
} VAL;
typedef struct OBJECT_KV {
VAL *key;
VAL *val;
} OBJECT_KV;
typedef struct OBJECT {
OBJECT_KV *array;
size_t len;
size_t cap;
} OBJECT;
OBJECT *OBJECT_new(void);
void OBJECT_delete(OBJECT *op);
VAL *VAL_new(void);
void VAL_delete(VAL *vp);
bool result_ok(OPS_RESULT res) { return res.error == OPS_RESULT_OK; }
const char *result_error_str(enum OPS_RESULT_errors err) {
switch (err) {
case OPS_RESULT_OK:
return "OK";
case OPS_RESULT_FALSE:
return "false";
case OPS_RESULT_UNIMPLEMENTED:
return "unimplemented";
case OPS_RESULT_INVALID_TYPE:
return "invalid type";
case OPS_RESULT_INVALID_INTEGER:
return "invalid integer";
default:
return "unknown error";
}
}
void result_print(OPS_RESULT res) {
FILE *out = stdout;
fprintf(out, "{error: \"%s\"", result_error_str(res.error));
if (result_ok(res) && res.val) {
res.val->ops->debug_print(res.val);
}
fprintf(out, "}");
}
VAL *result_unwrap(OPS_RESULT res) {
if (res.error != OPS_RESULT_OK) {
result_print(res);
printf("\n");
fflush(stdout);
abort();
}
return res.val;
}
void *xmalloc(size_t bytes) {
void *p = malloc(bytes);
if (!p)
abort();
return p;
}
void xfree(void *p) { free(p); }
void xrealloc(void **p, size_t bytes) {
void *new_p = realloc(*p, bytes);
if (!new_p)
abort();
*p = new_p;
}
// Got to take into account the virtual functions we are not using yet!
// One val may have reimplemented is_equal so check both ways. For SCIENCE!
// And unnecessary complexity!
OPS_RESULT VAL_is_equal(VAL *v1_p, VAL *v2_p) {
if (result_ok(v1_p->ops->is_equal(v1_p, v2_p)) &&
result_ok(v2_p->ops->is_equal(v2_p, v1_p)))
return (OPS_RESULT){.error = OPS_RESULT_OK};
return (OPS_RESULT){.error = OPS_RESULT_FALSE};
}
OPS_RESULT VAL_default_set_type(VAL *vp, enum VAL_types type_id) {
if (vp->type_id != VAL_UNDEFINED && vp->type_id != type_id)
// Would need to implement type conversion.
return (OPS_RESULT){.error = OPS_RESULT_UNIMPLEMENTED};
vp->type_id = type_id;
switch (type_id) {
case VAL_OBJECT:
vp->type_data.object = OBJECT_new();
break;
default:
// Do nothing special.
break;
}
return (OPS_RESULT){.error = OPS_RESULT_OK};
}
OPS_RESULT VAL_default_copy_from_int32(VAL *vp, int32_t source) {
int r;
switch (vp->type_id) {
case VAL_INT32:
vp->type_data.int32 = source;
break;
case VAL_STRING:
if (vp->type_data.string)
xfree(vp->type_data.string);
vp->type_data.string = xmalloc(MAX_INTSTRING_LEN);
r = snprintf(vp->type_data.string, MAX_INTSTRING_LEN, "%d", source);
if (r >= MAX_INTSTRING_LEN)
abort();
break;
default:
return (OPS_RESULT){.error = OPS_RESULT_INVALID_TYPE};
}
return (OPS_RESULT){.error = OPS_RESULT_OK};
}
OPS_RESULT VAL_default_copy_from_string(VAL *vp, const char *s) {
int r;
char *cp;
long lval;
switch (vp->type_id) {
case VAL_INT32:
errno = 0;
lval = strtol(s, &cp, 0);
if (errno == ERANGE || !(*cp == '\0' || isspace(*cp)) ||
!(lval <= INT_MAX && lval >= INT_MIN))
return (OPS_RESULT){.error = OPS_RESULT_INVALID_INTEGER};
vp->type_data.int32 = lval;
break;
case VAL_STRING:
if (vp->type_data.string)
xfree(vp->type_data.string);
r = strlen(s);
vp->type_data.string = xmalloc(r + 1);
strcpy(vp->type_data.string, s);
break;
default:
return (OPS_RESULT){.error = OPS_RESULT_INVALID_TYPE};
}
return (OPS_RESULT){.error = OPS_RESULT_OK};
}
// This is a move because it does not increment reference counts of key or val.
OPS_RESULT VAL_default_move_from_key_val(VAL *vp, VAL *key, VAL *val) {
// Must be an OBJECT
if (vp->type_id != VAL_OBJECT)
return (OPS_RESULT){.error = OPS_RESULT_INVALID_TYPE};
// Find existing key
size_t i;
for (i = 0; i < vp->type_data.object->len; i++) {
if (result_ok(VAL_is_equal(vp->type_data.object->array[i].key, key))) {
// Delete existing key and value
VAL_delete(vp->type_data.object->array[i].key);
VAL_delete(vp->type_data.object->array[i].val);
break;
}
}
// Insert new key and value
if (i == vp->type_data.object->len) {
// Might have to realloc.
if (i == vp->type_data.object->cap) {
if (vp->type_data.object->cap > 0)
vp->type_data.object->cap *= 2;
else
vp->type_data.object->cap = 4;
xrealloc((void **)&vp->type_data.object->array,
vp->type_data.object->cap * sizeof *vp->type_data.object->array);
}
vp->type_data.object->len++;
}
vp->type_data.object->array[i].key = key;
vp->type_data.object->array[i].val = val;
return (OPS_RESULT){.error = OPS_RESULT_OK};
}
OPS_RESULT VAL_default_is_equal(VAL *v1_p, VAL *v2_p) {
// Not going to do type conversion right now.
if (v1_p->type_id != v2_p->type_id)
return (OPS_RESULT){.error = OPS_RESULT_UNIMPLEMENTED};
switch (v1_p->type_id) {
case VAL_INT32:
if (v1_p->type_data.int32 != v2_p->type_data.int32)
return (OPS_RESULT){.error = OPS_RESULT_FALSE};
break;
case VAL_STRING:
if (strcmp(v1_p->type_data.string, v2_p->type_data.string) != 0)
return (OPS_RESULT){.error = OPS_RESULT_FALSE};
break;
default:
// Not going to compare OBJECTS right now. Too hard.
return (OPS_RESULT){.error = OPS_RESULT_UNIMPLEMENTED};
}
return (OPS_RESULT){.error = OPS_RESULT_OK};
}
OPS_RESULT VAL_default_debug_print(VAL *vp) {
FILE *out = stdout;
size_t i;
switch (vp->type_id) {
case VAL_INT32:
fprintf(out, "%d", vp->type_data.int32);
break;
case VAL_STRING:
fprintf(out, "\"%s\"", vp->type_data.string);
break;
case VAL_OBJECT:
fprintf(out, "{");
for (i = 0; i < vp->type_data.object->len; i++) {
if (i > 0)
fprintf(out, ", ");
vp->type_data.object->array[i].key->ops->debug_print(
vp->type_data.object->array[i].key);
fprintf(out, ": ");
vp->type_data.object->array[i].val->ops->debug_print(
vp->type_data.object->array[i].val);
}
fprintf(out, "}");
break;
default:
fprintf(out, "\"undefined type\"");
break;
}
return (OPS_RESULT){.error = OPS_RESULT_OK};
}
static const VAL_OPS VAL_OPS_template = {
.set_type = VAL_default_set_type,
.copy_from_int32 = VAL_default_copy_from_int32,
.copy_from_string = VAL_default_copy_from_string,
.move_from_key_val = VAL_default_move_from_key_val,
.is_equal = VAL_default_is_equal,
.debug_print = VAL_default_debug_print,
};
static const VAL VAL_template = {.type_id = VAL_UNDEFINED,
.ref_count = 1,
.type_data = {0},
.ops = &VAL_OPS_template,
.constant = false,
.owned_ptr = false};
VAL *VAL_new(void) {
VAL *p = xmalloc(sizeof *p);
*p = VAL_template;
return p;
}
void VAL_delete(VAL *vp) {
if (--vp->ref_count == 0) {
switch (vp->type_id) {
case VAL_STRING:
xfree(vp->type_data.string);
break;
case VAL_OBJECT:
OBJECT_delete(vp->type_data.object);
break;
default:
// Do nothing.
break;
}
xfree(vp);
}
}
static const OBJECT OBJECT_template = {0};
OBJECT *OBJECT_new(void) {
OBJECT *p = xmalloc(sizeof *p);
*p = OBJECT_template;
return p;
}
void OBJECT_delete(OBJECT *op) {
for (size_t i = 0; i < op->len; i++) {
VAL_delete(op->array[i].key);
VAL_delete(op->array[i].val);
}
xfree(op->array);
xfree(op);
}
int main(int argc, char *argv[]) {
VAL *top = VAL_new();
result_unwrap(top->ops->set_type(top, VAL_OBJECT));
for (int i = 1; i < argc - 1; i += 2) {
VAL *key = VAL_new();
VAL *val = VAL_new();
result_unwrap(key->ops->set_type(key, VAL_INT32));
// key->ops->copy_from_int32(key, i);
result_unwrap(key->ops->copy_from_string(key, argv[i]));
result_unwrap(val->ops->set_type(val, VAL_STRING));
// val->ops->copy_from_string(val, argv[i]);
result_unwrap(val->ops->copy_from_string(val, argv[i + 1]));
result_unwrap(top->ops->move_from_key_val(top, key, val));
}
top->ops->debug_print(top);
printf("\n");
VAL_delete(top);
return 0;
}
答案 1 :(得分:0)
union
个对象使用union
可以容纳几种不同类型之一。如果第一个成员是某个enum
成员,它声明union
中的哪个成员是活动成员,则称为 discriminated union 。
void
指针 void*
可以引用任何类型的对象,因此,如果对象本身存在于数组之外,则数组可以保存指向它们的指针。您仍然需要某种方式来记住对象的类型,例如同时包含enum
和void*
的结构。
char
由char
或unsigned char
组成的数组可以保存任何大小或更小的对象的对象表示形式。确保数组与max_align_t
对齐以确保它们具有正确的对齐方式来存储任何数据类型,或者仅指定正确对齐所需的多种类型。您可以将char*
转换为正确的指针类型。
使用union
可以为您完成所有工作。