Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointers to void pointers in C - can I use void** for rudimentary polymorphism?

I can understand how a void** might look in memory, but I'm wondering if I'm using it quite right. Are there any fundamental flaws in what I describe below? For example, although I can say "it works for me", am I creating bad / unportable code in some way?

So I have an Asteroids clone. There are three entities that can fire bullets, the players (SHIP *player_1, SHIP *player_2) and the UFO (UFO *ufo). When a bullet is fired, it's important to know who fired the bullet; if it was a player, when it hits something their score needs to be incremented. So, the bullet will store what kind of entity it belongs to (owner_type) and also a pointer directly to the owner (owner):

enum ShipType
{
    SHIP_PLAYER,
    SHIP_UFO
};

typedef struct Bullet
{ 
    // ...other properties
    enum ShipType owner_type;
    void **owner;
} BULLET;

Then, when the player hits the button or the UFO sees a target, one of these functions will be called:

void ship_fire(SHIP **shipp)
{
    BULLET *bullet = calloc(1, sizeof(BULLET));
    bullet->owner_type = SHIP_PLAYER;
    bullet->owner = (void**)shipp;
    // do other things
}

void ufo_fire(UFO **ufop)
{
    BULLET *bullet = calloc(1, sizeof(BULLET));
    bullet->owner_type = SHIP_UFO;
    bullet->owner = (void**)ufop;
    // do other things
}

... they may be called, for example, like this:

ship_fire(&player_1);

Finally, when the bullet hits a target (such as an asteroid), we dereference the owner. If it's a ship, we can increment the score there and then.

void hit_asteroid(ASTEROID *ast, BULLET *bullet)
{
    SHIP *ship_owner;
    if (bullet->owner_type == SHIP_PLAYER && *bullet->owner != NULL)
    {
        ship_owner = (SHIP*)*bullet->owner;
        ship_owner->score += 1000;
    }
}

Does that seem a reasonable approach? Like I say, it works for me, but I only have a couple of months of C experience.

A final note: why do I not use a void* instead of a void**? Because I want to avoid dangling pointers. In other words, say that player_1 dies and is free'd, but their bullet keeps going and hits an asteroid. If I only have a void*, the hit_asteroid function has no way of knowing that bullet->owner points to de-allocated memory. But with a void**, I can validly check to see if it's NULL; if player_1 is NULL, then *bullet->owner will be NULL too.

EDIT: All respondents so far concur that using a void** probably isn't necessary here because I can avoid the dangling pointers issue (by just statically allocating the base object, for instance). They're correct and I will refactor. But I'm still kinda interested to know if I've used void** in a way that might break something e.g. in terms of memory allocation / casting. But I guess if no-one has thrown their hands in the air and declared it faulty, it at least resembles something that would technically work.

Thanks!

like image 664
Gavin Avatar asked Dec 06 '22 03:12

Gavin


2 Answers

Even if you wanted to continue doing it the way you were, you don't need to use void ** (and shouldn't).

Although void * is a generic pointer type, void ** is not a generic pointer-to-pointer type - it should always point to a genuine void * object. Your code dereferences a SHIP ** or UFO ** pointer through an lvalue of type void ** - that's technically not guaranteed to work. (This happens when you do (SHIP*)*bullet->owner).

However, the good news is that you could continue to use the double-pointer method, using a plain void * to do the job. void * can happily store a pointer-to-a-pointer (because that, after all, is just another kind of pointer). If you change owner to void *, then in ship_fire you would do this:

bullet->owner = shipp;

and in hit_asteroid you would do this:

ship_owner = *(SHIP **)bullet->owner;

In general, the rule for working with pointer casts is: First cast the pointer back to the pointer type that you know it really is, then dereference.

like image 165
caf Avatar answered Dec 21 '22 23:12

caf


The linux kernel does this in an interesting way. It would be something like

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})


typedef struct Ship {
     void (*fire)(struct Ship * shipp);
     /* ...other methods...*/
} SHIP;

#define playership_of(shipp) container_of(shipp, PLAYERSHIP, ship)
#define ufoship_of(shipp) container_of(shipp, UFOSHIP, ship)

typedef struct PlayerShip {
    /* PlayerShip specific stuff ...*/
    SHIP ship;
    /*...*/
} PLAYERSHIP;

typedef struct UFOShip {
    /*...UFO stuff...*/
    SHIP ship;
    /*...*/
} UFOSHIP;

void ship_fire(SHIP * shipp)
{
     shipp->fire(shipp);
}

void player_fire(SHIP *shipp)
{
    PLAYERSHIP * ps = playership_of(shipp);
    BULLET *bullet = calloc(1, sizeof(BULLET));
    bullet->owner = shipp;
    // do other things
}

void ufo_fire(SHIP * shipp)
{
    UFOSHIP * ufos = ufoship_of(shipp);
    BULLET *bullet = calloc(1, sizeof(BULLET));
    bullet->owner = ufop;
    // do other things
}

UFOSHIP ufoship = { /*...*/ .ship = { .fire = ufo_fire } /* ... */ };
PLAYERSHIP playership = { /*...*/ .ship = { .fire = player_fire } /*...*/ };


/* ... */
ship_fire(&playership.ship);

Read the linux kernel source code for lots of examples of this tecnique.

like image 45
Tim Schaeffer Avatar answered Dec 21 '22 23:12

Tim Schaeffer