如何在C中使用包含多种对象或函数的数组

兰斯·波拉德

想知道是否可以执行以下操作将任意对象存储在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)函数一样建模,并将指向任何可能对象类型的通用指针传递给函数,以便它可以保存对它们的引用。想知道这是否可能,如果不能,那么如何做。

在功能方面,就像这样...

所以有显示了如何在一个空指针传递要获得任意类型跳出一个函数。

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() {

}

就我而言,我想拥有2个函数,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.

想知道是否有可能这样。

赞·山猫

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

考虑一下诸如Perl,Python或Javascript之类的脚本语言。每个变量都使用可以容纳不同类型值的变量。这些脚本语言均使用C编写。

那么他们如何做到的呢?

通常,它们使用并集和类型标记。类型标记通常是一个整数,就像您使用as一样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;
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在打字稿中使用多种类型的数组调用map函数

如何在数组中使用函数对象?

如何在NSUserDefaults中使用(多种类型)保存数组

如何在C ++中使用子函数创建对象?

如何在对象构造函数中使用 Map 数组函数

如何在函数php中使用对象

如何在VB中使用对象数组

如何在javascript中使用对象数组

如何在VBA函数中使用数组?

如何在JavaScript中使用数组和对象输入编写最快的选择函数?

如何在 React 中使用 map 函数显示对象数组中的图像链接?

如何在构造函数中使用int和String数组创建对象?

在awk中,如何在printf中使用包含多种格式字符串的文件?

如何在React函数组件中使用数组状态?

如何在 DynamoDB 中使用包含函数和 DynamoDBMapper Java?

如何在一个正则表达式对象中使用多种模式?

如何在C ++中使用数组?

C ++在构造函数中使用数组启动对象

如何在 C# 中使用其构造函数创建对象的副本?

如何在对象/方法对中使用回调函数

如何在调用函数的javascript对象中使用此参数?

如何在父类函数中使用继承的对象?

如何在php对象stdClass中使用参数调用函数?

如何在javascript中使用数组和对象

如何在每种方法中使用对象数组?

如何在数组对象中使用foreach

如何在Powershell中使用数组创建JSON对象

如何在JavaScript中使用对象转换数组?

如何在Android中使用For循环创建对象数组