C 中指向 void 指针的指针 - 我可以使用 void** 来实现基本多态性吗?

发布于 2024-08-20 20:07:03 字数 1990 浏览 6 评论 0原文

我可以理解 void** 在内存中的样子,但我想知道我使用它是否正确。我下面描述的内容是否存在根本性缺陷?例如,虽然我可以说“它对我有用”,但我是否以某种方式创建了糟糕/不可移植的代码?

所以我有一个小行星克隆。共有三个实体可以发射子弹,即玩家 (SHIP *player_1SHIP *player_2) 和 UFO (UFO *ufo)。当子弹发射时,知道谁发射了子弹是很重要的;如果是玩家,当它击中某物时,他们的分数需要增加。因此,子弹将存储它属于哪种实体(owner_type)以及直接指向所有者的指针(owner):

enum ShipType
{
    SHIP_PLAYER,
    SHIP_UFO
};

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

然后,当玩家点击按钮时或者 UFO 看到目标,将调用以下函数之一:

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
}

...它们可能会被调用,例如,如下所示:

ship_fire(&player_1);

最后,当子弹击中目标(例如小行星)时,我们取消引用所有者。如果是一艘船,我们可以立即增加分数。

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

这看起来是一个合理的方法吗?就像我说的,它对我有用,但我只有几个月的 C 经验。

最后一点:为什么我不使用 void* 而是使用 void**?因为我想避免悬空指针。换句话说,假设 player_1 死亡并获得自由,但他们的子弹继续前进并击中了小行星。如果我只有一个 void*,则 hit_asteroid 函数无法知道 bullet->owner 指向已取消分配的内存。但是使用 void**,我可以有效地检查它是否为 NULL;如果 player_1 为 NULL,则 *bullet->owner 也将为 NULL。

编辑:到目前为止,所有受访者都同意这里可能没有必要使用 void**,因为我可以避免悬空指针问题(例如,通过静态分配基础对象)。他们是正确的,我将重构。但我仍然有点想知道我是否使用 void** 的方式可能会破坏某些东西,例如在内存分配/转换方面。但我想,如果没有人举手宣布它有缺陷,那么它至少看起来在技术上是可行的。

谢谢!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

呆橘 2024-08-27 20:07:03

即使您想继续按照原来的方式进行操作,也不需要(也不应该)使用 void **

虽然 void * 是通用指针类型,但 void ** 不是通用指针到指针类型 - 它应该始终指向真正的 void * 对象。您的代码通过 void ** 类型的左值取消引用 SHIP **UFO ** 指针 - 从技术上讲,这不能保证有效。 (当您执行 (SHIP*)*bullet->owner 时会发生这种情况)。

然而,好消息是您可以继续使用双指针方法,使用普通的 void * 来完成这项工作。 void * 可以愉快地存储指向指针的指针(因为毕竟,这只是另一种指针)。如果将 owner 更改为 void *,那么在 ship_fire 中,您将执行以下操作:

bullet->owner = shipp;

hit_asteroid 中,您将执行 以下操作:一般而言

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

,使用指针强制转换的规则是:首先将指针强制转换回您知道的实际指针类型,然后取消引用。

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.

眼中杀气 2024-08-27 20:07:03

Linux 内核以一种有趣的方式做到了这一点。类似于

/**
 * 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);

阅读 linux 内核源代码以获取该技术的许多示例。

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.

得不到的就毁灭 2024-08-27 20:07:03

由于您只有两种可能的类型,因此我将使用联合来处理此类事情,如下所示:

typedef struct Bullet {
    enum ShipType owner_type;
    union {
        SHIP *ship;
        UFO *ufo;
    } owner;
} BULLET;

/* and then... */

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

请注意,我没有使用您使用的指针到指针方案。我不太相信它的必要性,而且我建议的代码不需要这样的技术。

Since you only have two possible types, I'd use a union for this sort of thing, like so:

typedef struct Bullet {
    enum ShipType owner_type;
    union {
        SHIP *ship;
        UFO *ufo;
    } owner;
} BULLET;

/* and then... */

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

Note that I didn't use the pointer-to-a-pointer scheme that you used. I'm not really convinced of the necessity of it, and the code I suggested doesn't require such a technique.

醉梦枕江山 2024-08-27 20:07:03

首先,检查 mipadi 建议的 union 结构;这是一种非常可读且有效的处理多态性的方法。

更接近您的片段/问题,快速浏览一下,我没有看到需要/使用指针到指针引入的双重间接。如果 xxxx_fire() 方法的参数是指向 xxxx 对象的[直接]指针(并且其余逻辑中的类型转换等也相应遵循),则整个逻辑将工作相同。

指向指针的指针在以下情况下很有用:中间指针的值可能会在某个时刻发生变化,例如,如果底层对象被移动,或者它被完全替换为不同的对象(例如游戏中新关卡的装备更好的船舶部分等)。 ...)

编辑:(关于使用双重间接寻址来管理可能被释放的对象“队列”
回应您的评论,请不要重构,以便当对象被杀死/销毁(作为游戏的一部分)时,对象不会被解除分配(从内存中)。相反,请查看如下内容,因为这确实是一个指针到指针构造有很大帮助的示例。它的工作原理如下:

  • 在游戏(或关卡)初始化时,分配一个足够大的指针数组,以包含与游戏随时间分配的对象总数一样多的指针。将其所有值初始化为 NULL。
  • 引入一个 int 值索引,它指示该数组中下一个可用(=到目前为止未使用)指针的位置。
  • 当创建一个新对象(UFO、船舶或其他什么)时,会发生四件事:
    • 为对象本身分配新内存
    • 这个新内存的地址存储在对象指针数组中(位于索引指示的位置)
    • 索引增加
    • “世界”仅通过双重间接知道这个对象
  • 当一个对象被销毁时, 会发生两件事
    • 内存被释放
    • 数组中的指针设置为 null
  • 程序会执行三件事
    • 第一次取消引用指针到指针
    • 检查它是否为空(如果是这样,则表明该对象不再存在,逻辑可能会决定从存储它的位置删除该引用,以便不再重试,但这当然是可选的)。
    • 通过取消引用中间指针(如果它不为 NULL)来访问实际对象

Insight,C 语言中的一个简短片段可能更明确;抱歉,我用文字描述了这一点......

First off, check the union construct suggested by mipadi; that's a very readable and efficient way of dealing with polymorphism.

Closer to your snippet/question, at a quick glance, I don't see the need/use for the double indirection introduced by pointer-to-pointers. The whole logic would work the same if the arguments to xxxx_fire() methods were [direct] pointers to xxxx objects (and if the typecast etc. in the rest of the logic were to follow accordingly.

Pointers to pointers are useful when the value of the intermediate pointer may be changed at some point. For example if the underlying object is moved, or if it replace by a different object altogether (say a better-equipped ship part of a new level in game etc...)

Edit: (on the use of double indirection to manage "fleets" of objects which may be deallocated.
Responding to your comment, do not refactor so that the objects are not de-allocated (from memory) when they get killed/detroyed (as part of the game). Instead, look into something like the following, as this is indeed an example where the pointer-to-pointer construct helps a lot. Here's how it could work:

  • Upon game (or level) initialization, allocate an array of pointers big enough to contain as many pointers as the total number of objects the game may allocate, over time. Initialize all its values to NULL.
  • Introduce an int value index, which indicates the location of the next available (=unused so far) pointer in this array.
  • When a new object (UFO, Ship or what have you) gets created, four things happen:
    • new memory is allocated for the object per se
    • the address of this new memory is stored in the object pointer array (at the location indicated by the index)
    • the index gets incremented
    • the "world" only knows this object by way of the double indirection
  • when an object gets destroyed two things happen
    • the memory is freed
    • the pointer in the array is set to null
  • when accessing any objects the program does three things
    • first dereference (once) the pointer-to-pointer
    • check if this is null (if so this indicate the object doesn't exist anymore, the logic may decide to remove this reference from wherever it stored it, as so to not try again, but this is of course optional).
    • access the actual object by dereferencing the intermediate pointer (if it isn't NULL)

In insight, a short snippet in C language may have been more explicit; sorry I described this in words...

仲春光 2024-08-27 20:07:03

如果你的项目符号所有者经常改变(例如释放),指针到指针的方法是合适的。工会解决方案并没有直接解决这个问题;如前所述,它不支持在不触摸该船的每个子弹上的指针的情况下取消分配船。当然,这在某些实现中实际上可能是一个实用的解决方案,例如,如果您需要查找给定玩家的所有子弹,您可以维护它们的链表:每个子弹的“next_bullet”指针和“last_bullet” ” 指向每个玩家列表头部的指针。

我不会单独分配每个项目符号,而是会遵循 mjv 的建议,预先分配其中一些项目符号并选择下一个可用的项目符号。在链表实现中,您可以使用相同的“next_bullet”指针来维护当前未使用的预分配项目符号列表。这种方法的优点是,如果您用完它们,您可以轻松分配更多,而不是维护数组,即,如果可用项目符号列表为空,只需根据需要将它们添加到列表中。同样,将“过期”(爆炸?)的子弹放回到可用子弹列表中,分配的数量将自动适应所需的数量。

我想到的另一件事是,您可能不需要知道哪个特定的 UFO(或其他敌人)拥有给定的子弹;只需为拥有玩家设置一个指针(例如 SHIP **),并将所有非玩家子弹的指针设置为 NULL。如果这不合适,您还可以考虑将每个所有者的类型存储在所有者结构本身的开头,例如:

enum EntityType { TYPE_PLAYER_SHIP, TYPE_UFO_SHIP, TYPE_BULLET, };

struct GameEntity {
    enum EntityType type;
    // This struct is not actually used, it just defines the beginning
};

struct Ship {
    enum EntityType type; // Set to TYPE_PLAYER_SHIP when allocating!
    …
};

struct UFO {
    enum EntityType type; // Set to TYPE_UFO_SHIP when allocating!
    …
};

struct Bullet {
    enum EntityType type;  // Set to TYPE_BULLET when allocating!
    struct GameEntity *owner;
    …
};

struct Bullet *ship_fire (struct Ship *ship) {
    Bullet *b = get_next_available_bullet();
    b->owner = (struct GameEntity *) ship;
    return b;
}

void hit_asteroid (struct Asteroid *ast, struct Bullet *bullet) {
    if (bullet->owner && bullet->owner->type == TYPE_PLAYER_SHIP) {
        …
    }
}

请注意,此技巧依赖于指向可互换的不同类型结构的指针,并且单个枚举存储在相同的位置每种类型的结构中的偏移量。实际上,这些并不是不合理的假设,但我不确定标准 C 中是否严格保证这种行为(但是,例如 struct sockaddr 使用相同的技巧,并且它被各种 POSIX 网络函数使用,例如绑定)。

If your bullet owners are frequently changed (e.g. deallocated), the pointer-to-pointer approach is suitable. The union solution does not address this concern directly; as presented, it does not support deallocating ships without touching the pointer on each of that ship's bullets. Of course, that may actually be a practical solution in some implementations, e.g. if you have a need to find all the bullets of a given player, you could maintain a linked list of them: a “next_bullet” pointer for each bullet and “last_bullet” pointer to the head of the list for each player.

And instead of allocating each bullet separately, I would also follow mjv's suggestion of pre-allocating some number of them and picking the next available one. In the linked list implementation, you could use the same “next_bullet” pointers to maintain one list of pre-allocated bullets not currently in use. The advantage of this approach is that you could easily allocate more if you ran out of them, instead of maintaining an array, i.e. if the list of available bullets is empty just add them to the list on demand. Similarly, put “expired” (exploded?) bullets back into the list of available ones and the amount allocated will automatically adapt to however many is required.

Another thing that comes to mind is that you might not need to know which particular UFO (or other enemy) owns a given bullet; just have a single pointer (e.g. SHIP **) for the owning player and set it to NULL for all non-player bullets. If this is not suitable, you could also consider storing the type of each owner in the beginning of owner struct itself, e.g.:

enum EntityType { TYPE_PLAYER_SHIP, TYPE_UFO_SHIP, TYPE_BULLET, };

struct GameEntity {
    enum EntityType type;
    // This struct is not actually used, it just defines the beginning
};

struct Ship {
    enum EntityType type; // Set to TYPE_PLAYER_SHIP when allocating!
    …
};

struct UFO {
    enum EntityType type; // Set to TYPE_UFO_SHIP when allocating!
    …
};

struct Bullet {
    enum EntityType type;  // Set to TYPE_BULLET when allocating!
    struct GameEntity *owner;
    …
};

struct Bullet *ship_fire (struct Ship *ship) {
    Bullet *b = get_next_available_bullet();
    b->owner = (struct GameEntity *) ship;
    return b;
}

void hit_asteroid (struct Asteroid *ast, struct Bullet *bullet) {
    if (bullet->owner && bullet->owner->type == TYPE_PLAYER_SHIP) {
        …
    }
}

Note that this trick relies on pointers to different types of structs being interchangeable, and the single enum being stored at the same offset in each type of struct. In practice these are not unreasonable assumptions, but I'm not certain that this behaviour is strictly guaranteed in standard C (however, e.g. struct sockaddr uses the same trick, and it's used by various POSIX networking functions like bind).

谜泪 2024-08-27 20:07:03

我会这样做:

enum _ShipType
    {
    SHIPT_PLAYER,
    SHIPT_UFO, //trailing , is good if you need to add types later
    };

typedef struct _Bullet
    { 
    // ...other properties
    struct _Bullet_Owner
        {
        enum _ShipType type;
        void* ship;
        }owner;
    } Bullet;

void ship_fire(Player* p)
    {
    Bullet* b = malloc(sizeof(Bullet));
    // ...other init
    b->owner.type = SHIPT_PLAYER;
    b->owner.ship = p;
    }

如果只有玩家,最好为每个玩家设置一个dead标志,并在他们死亡时进行设置。 (并让它们静态分配。)

#define PLF_DEAD 0x1
//more stuff

struct _Player
    {
    long flags;
    //other data;
    }player_1,player_2;

或者你可以有一个数组,或者...

编辑:非常量玩家,一个可怕的过度设计的解决方案:

typedef struct _PShip
    {
    long nweakrefs;
    void** weakrefs;
    //etc...
    }PShip;

PShip* PShip_new(/* args or void */)
    {
    PShip t;
    t = malloc(sizeof(PShip));
    t->nweakrefs = 1;
    t->weakrefs = malloc(sizeof(void*)*t->nweakrefs);
    //other stuff
    }
void PShip_regref(PShip** ref)
    {
    void** temp;
    temp = realloc((*ref)->weakrefs,(*ref)->nweakrefs);
    if(!temp){/* handle error somehow */}
    (*ref)->weakrefs = temp;
    (*ref)->weakrefs[(*ref)->nweakrefs++] = ref;
    }
void PShip_free(PShip* ship)
    {
    long i;
    for(i=0;i<ship->nweakrefs;i++)
        {
        if(ship->weakrefs[i]){*(ship->weakrefs[i]) = 0;}
        }
    //other stuff
    }

或者,引用计数可能会很好地工作,而不需要 O(n) 内存。

typedef struct _PShip
    {
    long refs;
    //etc...
    }PShip;

void Bullet_free(Bullet* bullet)
    {
    //other stuff
    if(bullet->owner.type == SHIPT_PLAYER)
        {
        if(--(((PShip*)(bullet->owner.ship))->refs) <= 0)
            {PShip_free(bullet->owner.ship);}
        }
    }

而且,这些都不是线程安全的。

I would do like this:

enum _ShipType
    {
    SHIPT_PLAYER,
    SHIPT_UFO, //trailing , is good if you need to add types later
    };

typedef struct _Bullet
    { 
    // ...other properties
    struct _Bullet_Owner
        {
        enum _ShipType type;
        void* ship;
        }owner;
    } Bullet;

void ship_fire(Player* p)
    {
    Bullet* b = malloc(sizeof(Bullet));
    // ...other init
    b->owner.type = SHIPT_PLAYER;
    b->owner.ship = p;
    }

If there's only <constant> players, you would be better off having a dead flag for each and setting when they die. (And having them statically allocated.)

#define PLF_DEAD 0x1
//more stuff

struct _Player
    {
    long flags;
    //other data;
    }player_1,player_2;

Or you could have an array, or...

Edit: Nonconstant players, a horrifically overengineered solution:

typedef struct _PShip
    {
    long nweakrefs;
    void** weakrefs;
    //etc...
    }PShip;

PShip* PShip_new(/* args or void */)
    {
    PShip t;
    t = malloc(sizeof(PShip));
    t->nweakrefs = 1;
    t->weakrefs = malloc(sizeof(void*)*t->nweakrefs);
    //other stuff
    }
void PShip_regref(PShip** ref)
    {
    void** temp;
    temp = realloc((*ref)->weakrefs,(*ref)->nweakrefs);
    if(!temp){/* handle error somehow */}
    (*ref)->weakrefs = temp;
    (*ref)->weakrefs[(*ref)->nweakrefs++] = ref;
    }
void PShip_free(PShip* ship)
    {
    long i;
    for(i=0;i<ship->nweakrefs;i++)
        {
        if(ship->weakrefs[i]){*(ship->weakrefs[i]) = 0;}
        }
    //other stuff
    }

Alternatively, a reference count might work well, without the O(n) memory.

typedef struct _PShip
    {
    long refs;
    //etc...
    }PShip;

void Bullet_free(Bullet* bullet)
    {
    //other stuff
    if(bullet->owner.type == SHIPT_PLAYER)
        {
        if(--(((PShip*)(bullet->owner.ship))->refs) <= 0)
            {PShip_free(bullet->owner.ship);}
        }
    }

Also, neither of these is threadsafe.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文