【问题标题】:How to have an array with multiple types of objects, or a function, in C如何在 C 中拥有具有多种类型对象或函数的数组
【发布时间】:2019-03-17 21:29:15
【问题描述】:

想知道您是否可以这样做以将任意对象存储在 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() {

}

在我的例子中,我想要两个函数,pushpop,它们可以处理任意数据。

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.

想知道这样的事情是否可行。

【问题讨论】:

  • 您需要在结构中添加一些指示类型的内容,其中包含不透明的数据指针。
  • free 实际上只需要地址,而不是其中的内容。但这是一个不同的故事。无论如何,您要创建的是动态元组。您可能想在 SO 上进行搜索,肯定会弹出类似的内容。
  • @edmz "dynamic tuple in c" 在搜索方面没有带来任何好处。

标签: c arrays struct


【解决方案1】:

是的,这是可能的。只是很难正确地做到。

想想 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;
}

【讨论】:

  • 介意用一些代码演示吗?不过,这让我可以搜索一下,谢谢!
  • @LancePollard 这是一些 Perl 文档的链接,可以显示 API 及其复杂程度。 perldoc.perl.org/perlguts.html
【解决方案2】:

存储union 对象

union 可让您持有几种不同类型中的一种。如果第一个成员是某种enum,它声明union 的哪个成员是活动的,则称为可区分联合

存储 void 指针

void* 可以引用任何类型的对象,因此如果对象本身存在于数组之外,则数组可以保存指向它们的指针。您仍然需要一些方法来记住对象的类型,例如包含enumvoid* 的结构。

存储char 的数组

charunsigned char 的数组可以保存任何大小或小于其大小的对象的对象表示。确保数组与max_align_t 对齐,以保证它们具有正确的对齐方式来存储任何数据类型,或者只指定需要正确对齐的多种类型。您可以将char* 转换为正确的指针类型。

使用union 为您处理这一切..

【讨论】:

    猜你喜欢
    • 2014-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-16
    • 1970-01-01
    • 2020-04-04
    • 1970-01-01
    • 2019-09-23
    相关资源
    最近更新 更多