Как получить массив с несколькими типами объектов или функцию в C - PullRequest
3 голосов
/ 18 марта 2019

Хотите знать, можете ли вы сделать что-то подобное для хранения произвольных объектов в массиве в 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, чтобы получить произвольные типы из функции.

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.

Интересно, возможно ли что-нибудь подобное.

Ответы [ 2 ]

3 голосов
/ 18 марта 2019

Да, это возможно. Просто очень сложно сделать правильно.

Подумайте о таких языках сценариев, как Perl, Python или Javascript. Каждый из них использует переменные, которые могут содержать различные типы значений. Каждый из этих языков сценариев написан на C.

Так как они это делают?

Обычно они используют объединения и теги типа. Тег типа часто является целым числом, как вы используете как szType. Иногда они являются указателем на структуру с данными о типе. Иногда это комбинация, потому что все целочисленные теги меньше 0x1000 (например), поэтому любое большее число должно быть указателем.

Итак, разработайте C union, который может хранить данные обо всех ваших типах данных. Включите указатель, чтобы не очень большие типы не делали каждый тип огромным. Затем спроектируйте структуру, содержащую тег типа и одно из ваших объединений.

Затем для каждой функции, которую вы создаете для манипулирования этими структурами, проверьте теги типов и выполните правильные операции для каждой из них.

Мне было скучно. Вот код Обратите внимание, что это 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;
}
0 голосов
/ 18 марта 2019

Магазин union Объекты

A union позволяет вам держать один из нескольких различных типов. Если первый член имеет вид enum, который объявляет, какой член union активен, это называется дискриминационным объединением .

Магазин void Указатели

A void* может ссылаться на любой тип объекта, поэтому, если сами объекты существуют вне массива, массив может содержать указатели на них. Вам все еще нужен какой-то способ запомнить тип объектов, такой как структура, содержащая как enum, так и void*.

Магазин массивов char

Массив char или unsigned char может содержать представление объекта любого объекта, его размер или меньше. Убедитесь, что массивы выровнены по max_align_t, чтобы гарантировать правильное выравнивание для хранения любого типа данных, или просто укажите несколько типов, которые необходимо правильно выровнять. Вы можете привести char* к правильному типу указателя.

Использование union обрабатывает все это для вас ..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...