Void * как общий в C, безопасен ли он?

приготовьтесь к вопросу немного "скрученным"...

В прошлом я реализовал много структуры данных (дерево, список, хеш-таблицу, график), используя макрос, я могу реализовать некоторый вид o generic. Однако я блуждал, если можно реализовать общую структуру данных с помощью указателя void, но каким-то образом я хотел бы иметь возможность использовать typecheking...

Я не знаю, ясно ли, что я пытаюсь сказать... но в принципе я не думаю, что всегда безопасно ставить "void *" как общий, в то же время я не думаю, всегда полезно использовать макрос как способ создания общей структуры данных (так как в основном то, что делает препроцессор с макросом, это замена кода), потому что если вы посмотрите вокруг Интернета, вы можете найти такие примеры.

Хорошей идеей может быть, на мой взгляд, но, вероятно, я не прав, это использовать макрос для создания стандартного интерфейса для данных, хранящихся в структуре данных, среди функций интерфейса я бы поставил код для правильного типа проверяя, учитывая пустоту *. Вдохновленный инженером-разработчиком программного обеспечения, это может быть хорошим способом продолжить.

Конечно, верно, что для слишком сложного материала было бы лучше переключить язык (С++/Java), но даже верно, что это не всегда возможно.

Итак, вкратце... как обычно обрабатывается проблема "generic" в C? я полагаюсь на ваш опыт для ответа!

Ответ 1

Вкратце, нет никакого удобного способа получить типовые структуры и функции данных типа C в.

необщего:

struct node {
  int value;
  struct node *next;
};

Общий, но небезопасный-a void* не имеет информации о типе:

struct node {
  void *value;
  struct node *next;
};

Безопасный, но уродливый:

#define DECLARE_NODE_TYPE(type) \
  struct node_##type { \
    type value; \
    struct node_##type *next; \
  };

DECLARE_NODE_TYPE(int)
node_int *x = ...

Такая же идея, но немного менее уродливая:

// declare_node_type.h

struct node_##NODE_TYPE {
  NODE_TYPE value;
  struct node_##NODE_TYPE *next;
};

#undef NODE_TYPE

// elsewhere

#define NODE_TYPE int
#include "declare_node_type.h"

node_int *x = ...

Общий и безопасный, но С++, а не C:

template<typename T>
struct node {
  T value;
  node<T> *next;
};

node<int> *x = ...

Ответ 2

Вы можете сделать более безопасный материал с помощью void*; вернувшись к связанному примеру Джона Перди:

typedef struct {
    union {
        void* data;  // generic data
        int idata;   // int is not stored dynamically
    };
    int type;    // additional type information
    Node* next;  // link
} Node;

#define NODE_TYPE_INT 0
Node* createNodeInt(Node* self, Node* next, int value) {
    self->idata = value;
    self->type = NODE_TYPE_INT;
    self->next = next;
    return self;
}

// in this case relying on user defined types...
Node* createNodeGeneric(Node* self, Node* next, void* data, int type) {
    assert(type != NODE_TYPE_INT && ..);
    self->data = data;
    self->type = type;
    self->next = next;
    return self;
}

Другой подход состоит в том, чтобы использовать обычный первый элемент в качестве базового типа:

typedef struct {
    int type;
} Node;

#define TYPE_BINARY 0
typedef struct {
    Node base;
    Node* left;
    Node* right;
    int op;
} BinaryOp;

#define TYPE_LEAF_INT 1
typedef struct {
    Node base;
    int a;
} LeafInt;

#define TYPE_LEAF_FLOAT 2
typedef struct {
    Node base;
    float b;
} LeafFloat;

void op(BinaryOp* node) {
    switch(node->left.type) {
    case TYPE_BINARY:
        op((BinaryOp*)node->left);
        break;
    case TYPE_LEAF_INT:
        evalInt((LeafInt*)node->left);
        break;
    ...
    }
}

Node* foo() {
    LeafInt* left;
    LeafFloat* right;
    BinaryOp* op;
    // allocate
    ...
    // init
    createLeafInt(left, 42);
    createLeafFloat(right, 13.37);
    createBinaryOp(op, &left->base, &right->base);
    // and return
    return &op->base;
}
}