Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

void* as generic in C, is it safe?

Tags:

c

generics

get ready for a question a bit "twisted"...

I've implemented in the past a lot of data structure (tree, list, hash table, graph as well), using the macro i can implement some kind o generic. However i was wandering if it is possible to implement generic data structure using void pointer but somehow i would like to be able to use a typecheking...

I don't know if it is clear what i'm trying to say... but basically i don't think it is always safe to put "void*" as generic, at the same time i don't think is always a good idea to use the macro as way to make a generic data structure (since basically what a preprocessor does with the macro does is code substitution), because if you look around the web you can find such examples.

A good idea might be, in my opinion but probably i'm not right, is to use the macro for make a standard interface for the data stored in a data structure, among the interface functions i would put code for correct type checking, given a void*. Inspired by the software engineer techinique this could be a good way to proceed.

It is surely true that probably for too sofisticated stuff would be better to switch language (C++/Java) but it is even true that this is not always possible.

So in summary... how the problem of "generic" in C is usually handled? i rely on your experience for an answer!

like image 207
user8469759 Avatar asked Aug 06 '15 23:08

user8469759


People also ask

Are void pointers generic?

// void pointer void *ptr; double d = 9.0; // valid code ptr = &d; The void pointer is a generic pointer that is used when we don't know the data type of the variable that the pointer points to.

What does void *) mean in C?

void (C++) If a pointer's type is void* , the pointer can point to any variable that's not declared with the const or volatile keyword. A void* pointer can't be dereferenced unless it's cast to another type. A void* pointer can be converted into any other type of data pointer.

Is generic pointer same as void pointer?

No difference. void pointer is itself called generic pointer.

Why are void pointers bad?

There is only one drawback, which is that polymorphism based on void * is unsafe: once you cast a pointer to void *, there is nothing that prevents you from casting that void * to the wrong pointer type by mistake.


2 Answers

Briefly, there’s no convenient way to get type-safe generic data structures and functions in C.

Non-generic:

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

Generic, but unsafe—a void* has no type information:

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

Safe, but ugly:

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

DECLARE_NODE_TYPE(int)
node_int *x = ...

Same idea, but slightly less ugly:

// 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 = ...

Generic and safe, but C++, not C:

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

node<int> *x = ...
like image 106
Jon Purdy Avatar answered Nov 15 '22 18:11

Jon Purdy


You can do safer stuff with void*; getting back to the linked example of Jon Purdy:

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

Another approach is to use the common first member as the base type:

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;
}
}
like image 24
BeyelerStudios Avatar answered Nov 15 '22 19:11

BeyelerStudios