Необходимо руководство по размещению структур c и массивов в куче в Objective-c.

Я работаю над своим первым большим приложением на Objective-C, карточной игрой. По разным причинам я решил поддерживать состояние моей игры с помощью структуры C, которая содержит целые числа, логические значения, массивы и различные указатели. (Для любопытства, я делаю это, потому что в конечном итоге мне придется создавать и уничтожать тысячи и тысячи этих gameStates, и я хотел быть как можно ближе к «голому железу».) Структура gameState определяется следующим образом:

struct GameState {
    // changing game state properties
    int score;
    int moves;
    bool won;

    int numberOfCards;
    int numberOfPiles;

    // doneness of piles
    bool *pileDoneness;
    bool *pileDisabled;

    // array of cards (each item is a pointer to a card)
    Card *cards;

    // defining piles (each card in the pile is a pointer to a pointer Card in the cards array)
    Card ***piles;

    // previous gameStates
    struct GameState *previous;
};

Одно примечание: прямо перед тем, как сделать ход в игре, я копирую gameState (все значения, кроме предыдущего) и устанавливаю для копии gameState->previous.

Карта — это просто целое число, в котором различные байты представляют различные свойства карты (масть, лицом вверх/вниз, значение и т. д.).

У меня также есть класс MDCard и класс MDGameState. Оба этих класса имеют только методы класса, и я никогда не создаю их экземпляры. У них есть методы, которые могут создавать и манипулировать картами и игровыми состояниями соответственно. Чтобы объяснить, вы можете перевернуть карту, используя этот код:

// assume card already exists
[MDCard flip:card];

Я могу получить цвет карты следующим образом:

Color color = [MDCard color:card];

Хорошо. Теперь объект MDGameState также имеет метод класса для создания нового экземпляра игры. Это его код:

+(GameState *)newGameStateWithNumberOfPiles:(u_int)numberOfPiles andNumberOfCards:(u_int)numberOfCards{
    // create our new gameState struct
    GameState *gameState = malloc(sizeof(GameState));

    // array of state of doneness of piles
    gameState->pileDoneness = malloc(sizeof(bool) * numberOfPiles);
    for (int i = 0 ; i < numberOfPiles ; i++) gameState->pileDoneness[i] = false;

    gameState->pileDisabled = malloc(sizeof(bool) * numberOfPiles);
    for (int i = 0 ; i < numberOfPiles ; i++) gameState->pileDisabled[i] = false;

    // array of pointers to cards
    **gameState->cards = malloc(sizeof(Card) * numberOfCards);**
    for (int i = 0 ; i < numberOfCards ; i++) gameState->cards[i] = 0;

    // array of array of pointers to Cards in cards array
    gameState->piles = malloc(sizeof(Card **) * numberOfPiles);
    for (int p = 0 ; p < numberOfPiles ; p++){
        gameState->piles[p] = malloc(sizeof(Card *) * numberOfCards);
        for(int c = 0 ; c < numberOfCards ; c++){
            gameState->piles[p][c] = nil;
        }
    }

    gameState->moves = 0;
    gameState->score = 0;
    gameState->won = false;

    // piles
    gameState->numberOfPiles = numberOfPiles;
    gameState->numberOfCards = numberOfCards;
    gameState->previous = nil;

    return gameState;
}

В настоящее время я понимаю, что gameState будет размещен в куче. На самом деле переменная gameState — это просто указатель на выделенную память в куче. Эта выделенная память в куче содержит различные значения, такие как количество ходов, счет, выигрыш и т. д. Она также содержит указатели на другую выделенную память в куче для массивов gameState карт, стопок и т. д. IE: все mallocs выделение памяти в куче.

Я знаю, что мне нужно освободить память для этого состояния игры, когда я закончу с этим.

Мне интересно, отслеживает ли Objective-C ARC ссылки на эту структуру gameState? Как насчет массивов, на которые он ссылается? В настоящее время я предполагаю, что ответ отрицательный.

Я предполагаю, что как только gameState возвращается из newGameStateWithNumberOfPiles:andNumberOfCards: класс MDGameState больше не имеет на него ссылки. Это правильно?

Я предполагаю, что объект, который заканчивается ссылкой на gameState, отвечает за освобождение своей памяти, когда это делается с gameState?

Мне нужно, чтобы объект gameState жил, пока пользователь играет в игру. Опять же, обратите внимание, что каждый раз, когда пользователь делает ход, создается копия gameState, которая устанавливается как предыдущая версия gameState. Это для отмены.

Я создал функцию в MDGameState, которая должна освободить всю память, используемую этой структурой:

+ (void)freeGameState:(GameState *)gameState recurse:(BOOL)recurse {
    if(recurse && gameState->previous != nil){
        [MDGameState freeGameState:gameState->previous recurse:recurse];
    }

    for (int p = 0 ; p < gameState->numberOfPiles ; p++){
        free(gameState->piles[p]);
    }

    free(gameState->piles);
    free(gameState->pileDoneness);
    free(gameState->pileDisabled);
    free(gameState->cards);
}

Обратите внимание, что в некоторых случаях я могу захотеть освободить текущее состояние игры, но не предыдущие состояния. Например, если я отменяю ход.

Итак, когда пользователь запускает новую игру, создается gameState и возвращается в мой viewController, который сохраняет эту ссылку. Когда пользователь запускает другую игру, я сначала вызываю [MDGameState freeGameState:gameState recurse:YES], чтобы полностью очистить предыдущее состояние gameState, а затем я создаю совершенно новое состояние игры, используя [MDGameState newGameStateWithNumberOfPiles:13 andNumberOfCards:52].

Я думаю, что правильно убираю за собой. Однако инструменты говорят мне, что у меня есть утечки памяти. Он специально выделяет выделенную жирным шрифтом строку в newGameStateWithNumberOfPiles:andNumberOfCards: пример выше. Это происходит почти сразу после того, как я начинаю новую игру. Он также выделяет идентичный код в функции, которая создает копию gameState. Я не понимаю, почему этот malloc выделен, а два над ним нет. Если я изменю порядок malloc в этой функции, он все равно выделит malloc для карт.

Есть шанс, что у вас есть какие-нибудь мысли по этому поводу? Спасибо за вашу помощь!


person Doug Hughes    schedule 16.02.2013    source источник
comment
Для справки: для этого лучше использовать объекты. Objective-C ненамного медленнее простого C, но для некоторых задач удобнее.   -  person    schedule 17.02.2013
comment
Я полностью понимаю. Это также своего рода эксперимент, который поможет мне лучше изучить C и то, как он соотносится с Objective-C.   -  person Doug Hughes    schedule 17.02.2013
comment
Это гораздо лучший аргумент. В этом случае я не продолжаю мудрить.   -  person    schedule 17.02.2013


Ответы (1)


Вы правы, ARC не управляет памятью, выделенной с помощью malloc().

У вас есть утечка в методе +freeGameState:recurse:, потому что вы никогда не вызываете free(gameState), что означает утечку каждого отдельного gameState.

Правильно, класс MDGameState не будет иметь ссылки на gameState после возврата из +newGameStateWithNumberOfPiles:andNumberOfCards.

Да, вызывающий newGameStateWithNumberOfPiles:andNumberOfCards отвечает за освобождение памяти, когда это делается с помощью gameState.

person monoxygen    schedule 16.02.2013
comment
Вы правы, хотя оказалось, что у меня было две утечки памяти. Другой был в функции, которая создавала карточки для gameState-›cards. Я выделил массив пустых (ok, 0) карт при создании gameState, а затем заменил их другим массивом перетасованных карт. Мне вообще не нужно было выделять пустой массив и не освобождать его при замене, отсюда и утечка. Спасибо за помощь! - person Doug Hughes; 17.02.2013