C의 void 포인터에 대한 포인터 - 기본적인 다형성에 void**를 사용할 수 있나요?

StackOverflow https://stackoverflow.com/questions/2222130

  •  19-09-2019
  •  | 
  •  

문제

나는 어떻게 이해할 수 있습니다 void** 메모리를 보면 알겠지만 제가 제대로 사용하고 있는지 궁금합니다.아래에 설명하는 내용에 근본적인 결함이 있습니까?예를 들어, "저에게 적합합니다"라고 말할 수 있지만 어떤 방식으로든 나쁘거나 이식할 수 없는 코드를 만들고 있는 걸까요?

그래서 나는 소행성 클론을 가지고 있습니다.총알을 발사할 수 있는 엔터티는 세 가지가 있습니다. 플레이어(SHIP *player_1, SHIP *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**를 사용한 적이 있는지 알고 싶습니다.메모리 할당/캐스팅 측면에서.그러나 아무도 손을 높이 들고 결함이 있다고 선언하지 않았다면 적어도 기술적으로 작동하는 것과 비슷할 것입니다.

감사해요!

도움이 되었습니까?

해결책

당신이 그대로 계속하고 싶었더라도 사용할 필요가 없습니다. void ** (그리고해서는 안됩니다).

하지만 void * 일반적인 포인터 유형이며 void ** ~이다 ~ 아니다 일반적인 포인터 투 포인터 유형-항상 진정한 것을 가리 려야합니다. void * 물체. 귀하의 코드는 a SHIP ** 또는 UFO ** 유형의 LValue를 통한 포인터 void ** - 기술적으로 작동하지 않습니다. (이것은 당신이 할 때 발생합니다 (SHIP*)*bullet->owner).

그러나 좋은 소식은 평원을 사용하여 더블 포인터 방법을 계속 사용할 수 있다는 것입니다. void * 일을하기 위해. void * 행복하게 포인터-포인터를 저장할 수 있습니다 (결국 또 다른 종류의 포인터이기 때문에). 당신이 변하면 owner 에게 void *, 그런 다음 ship_fire 당신은 이것을 할 것입니다 :

bullet->owner = shipp;

그리고에서 hit_asteroid 당신은 이것을 할 것입니다 :

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

일반적으로 포인터 캐스트로 작업하는 규칙은 다음과 같습니다. 먼저 포인터를 실제로 알고있는 포인터 유형으로 다시 캐스팅합니다. 그 다음에 불평.

다른 팁

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

이 tecnique의 많은 예에 대해서는 Linux 커널 소스 코드를 읽으십시오.

당신은 두 가지 유형 만 가지고 있기 때문에, 나는 이런 종류의 일에 노조를 사용할 것입니다.

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

사용한 포인터 투 포인터 체계를 사용하지 않았습니다. 나는 실제로 그것의 필요성을 확신하지 못하고, 내가 제안한 코드에는 그러한 기술이 필요하지 않습니다.

먼저, 확인해 보세요. 조합 구성 mipadi가 제안한;그것은 매우 읽기 쉽고 효율적인 방법입니다. 다형성 다루기.

스니펫/질문에 더 가까이 다가가면 포인터 간 포인터에 의해 도입된 이중 간접 참조의 필요성/사용이 한눈에 보이지 않습니다.xxxx_fire() 메서드에 대한 인수가 xxxx 개체에 대한 [직접] 포인터인 경우(그리고 유형 변환 등이 있는 경우) 전체 논리는 동일하게 작동합니다.나머지 논리에서는 그에 따라 따라야했습니다.

포인터에 대한 포인터는 중간 포인터의 값이 어떤 지점에서 변경될 수 있을 때 유용합니다..예를 들어 기본 객체가 이동되거나 완전히 다른 객체로 대체되는 경우(예: 게임의 새 레벨에서 더 나은 장비를 갖춘 선박 부분 등...)

편집하다:(에 할당 해제될 수 있는 객체의 "플릿"을 관리하기 위해 이중 간접 사용.
귀하의 의견에 응답하여 ~ 아니다 객체가 다음과 같이 리팩토링됩니다. ~ 아니다 (게임의 일부로) 죽거나 삭제되면 할당이 해제됩니다(메모리에서). 대신에, 다음과 같은 내용을 살펴보십시오. 이는 실제로 포인터 간 구조가 많은 도움이 되는 예이기 때문입니다.작동 방법은 다음과 같습니다.

  • 게임(또는 레벨) 초기화 시 배열을 할당합니다. 포인터의 시간이 지남에 따라 게임이 할당할 수 있는 총 개체 수만큼 많은 포인터를 포함할 수 있을 만큼 충분히 큽니다.모든 값을 NULL로 초기화합니다.
  • 이 배열에서 다음 사용 가능한(=지금까지 사용되지 않은) 포인터의 위치를 ​​나타내는 int 값 인덱스를 도입합니다.
  • 새로운 개체(UFO, 선박 또는 무엇을 가지고 있는지)가 생성되면 네 가지 일이 발생합니다.
    • 객체 자체에 새로운 메모리가 할당됩니다.
    • 이 새 메모리의 주소는 객체 포인터 배열(인덱스로 표시된 위치)에 저장됩니다.
    • 인덱스가 증가합니다
    • "세계"는 이중 간접 참조를 통해서만 이 객체를 알고 있습니다.
  • 객체가 파괴되면 두 가지 일이 발생합니다
    • 메모리가 해제되었습니다
    • 배열의 포인터가 null로 설정되었습니다.
  • 어떤 객체에 접근할 때 프로그램은 세 가지 일을 합니다.
    • 첫 번째 역참조(한 번) 포인터 간 포인터
    • 이것이 null인지 확인합니다(그렇다면 객체가 더 이상 존재하지 않는다는 것을 나타내는 경우 논리는 다시 시도하지 않기 위해 이 참조를 저장한 위치에서 제거하기로 결정할 수 있지만 물론 선택 사항입니다).
    • 중간 포인터를 역참조하여 실제 객체에 액세스합니다(NULL이 아닌 경우).

통찰력에 있어서 C 언어의 짧은 조각이 더 명확했을 수도 있습니다.미안 이걸 말로 설명했어...

만약에 총알 소유자는 자주 변경됩니다 (예 : 거래). 포인터 투 포인터 접근 방식이 적합합니다. Union 솔루션은이 문제를 직접 해결하지 못합니다. 제시된 바와 같이, 그것은 각 배의 총알에 포인터를 만지지 않으면 서 거래선을 지원하지 않습니다. 물론, 그것은 일부 구현에서 실제로 실용적인 솔루션 일 수 있습니다. 예를 들어 주어진 플레이어의 모든 총알을 찾을 필요가 있다면 링크 된 목록을 유지할 수 있습니다. 각 총알에 대한 "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 네트워킹 기능에서 사용됩니다. bind).

나는 이것을 좋아할 것이다 :

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

만 있다면u003Cconstant> 플레이어, 당신은 a 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);}
        }
    }

또한 이들 중 어느 것도 나사산이 아닙니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top