Указатель на указатель структур, индексирующих вне границ (?), Когда я пытаюсь индексировать что-либо кроме нуля - PullRequest
0 голосов
/ 11 марта 2020

Я пытаюсь редактировать массив (как указатель) структур, заполняя значения по умолчанию новыми структурами, которые я инициализирую. Это, кажется, вызывает некоторые действительно странные проблемы. Я учусь использовать структуры с указателями, поэтому любая помощь приветствуется.

Фрагмент основной функции (игрок просто сохраняет startLo c без его изменения)

Location** near;
    startLoc = initLocation("Base", 5);
    player = initPlayer(startLoc);
    near = &(startLoc->near);
    *near = initLocation("Zero", 0);
    *(near + 1) = initLocation("Two", 0);
    printf("Start near: %i\n", startLoc->nearCount);

Вся локация. c

#include <stdlib.h>

typedef struct Location {
    char isValid;
    char* name;
    struct Location* near;
    int nearCount;
} Location;

Location* initLocation(char* name, int nearCount) {
    Location* l = malloc(sizeof(Location));
    l->name = name;
    l->near = calloc(sizeof(Location) * nearCount, 1);
    l->nearCount = nearCount;
    l->isValid = 1;
    return l;
}

1 Ответ

3 голосов
/ 11 марта 2020

Давайте начнем с базового c обсуждения о указателе и указателе на указатель . Указатель - это просто переменная, которая содержит адрес чего-то другого в качестве значения. Когда вы объявляете указатель на что-то, как вы сделали с вашими name или near членами в вашей структуре, вы объявляете переменную, которая будет содержать адрес в памяти , где это Тип объекта хранится в памяти (например, указатель указывает на то место, где хранится этот объект)

Когда вы объявляете указатель на указатель для ввода (например, Location **near) у вас есть указатель , который содержит адрес другого указателя в качестве значения. Это может быть полезно двумя способами. (1) он может позволить вам передать адрес указателя как параметр , чтобы функция могла работать с исходным указателем по этому адресу, или (2) он может разрешить этот единственный указатель на указывать на коллекцию указателей в памяти, например,

 pointer
    |      pointers      allocated struct
   near --> +----+     +-------------------+
            | p1 | --> | struct Location 1 |
            +----+     +-------------------+
            | p2 | --> | struct Location 2 |
            +----+     +-------------------+
            | p3 | --> | struct Location 3 |
            +----+     +-------------------+
            | .. |     |        ...        |

    (a pointer-to-pointer to type struct Location)

Во втором случае, зачем выбирать указатель-на-указатель в качестве типа, а не просто выделять для коллекции этого тип? Хороший вопрос. Есть две основные причины, одна из которых может быть связана с тем, что то, что вы выделяете, может различаться по размеру. Например:

 char**
    |      pointers      allocated strings
  words --> +----+     +-----+
            | p1 | --> | cat |
            +----+     +-----+--------------------------------------+
            | p2 | --> | Four score and seven years ago our fathers |
            +----+     +-------------+------------------------------+
            | p3 | --> | programming |
            +----+     +-------------------+
            | .. |     |        ...        |

или (2), где вы хотите выделенную коллекцию четного числа объектов (например, изменение char** выше на int**), к которым можно обратиться с помощью индексации двумерного массива (например, array[2][7])

Выделение для коллекции указателей и объектов добавляет сложности, поскольку вы несете ответственность за поддержание двух выделенных коллекций, указателей и объектов самих себя. Вы должны отслеживать и перераспределять как свою коллекцию указателей (и объектов - если необходимо), так и free() свою коллекцию объектов, прежде чем освободить выделенный блок указателей.

Это может быть значительно упрощено, если вам просто нужно некоторое число объекта того же типа, например, N - struct Location. Это дает вам одно выделение, одно перераспределение и одно освобождение для самих этих объектов (конечно, каждый объект может в свою очередь содержать также выделенные объекты). В вашем случае для near это будет похоже на:

 pointer
    |
   near --> +-------------------+
            | struct Location 1 |
            +-------------------+
            | struct Location 2 |
            +-------------------+
            | struct Location 3 |
            +-------------------+
            |        ...        |

       (a pointer to type struct Location)

В вашем случае вам нужно иметь вложенных выделенных блоков struct Location. В этом смысле, где требуется, вам просто нужен N - struct Location, который будет иметь одинаковый размер, и нет необходимости в индексации двумерных массивов. С этой точки зрения, кажется, что гораздо более разумно смотреть на то, что вы пытаетесь сделать (в лучшем случае), просто выделять для блоков struct Location, а не обрабатывать отдельные блоки указателей, указывающих на индивидуально выделенные struct Location.

Реализация краткого примера

Хотя с initLocation() нет ничего плохого в настройке одного struct Location, вы можете обнаружить, что имеет больше смысла просто напишите addLocation() функцию для добавления новой struct Location в вашу коллекцию каждый раз, когда она вызывается. Если вы инициализируете свой указатель на коллекцию NULL обратно в вызывающей стороне, вы можете просто использовать realloc() для обработки вашего первоначального выделения и последующих перераспределений.

В следующем примере мы просто создаем новый struct Location для каждого имени в списке и выделить для 3- near объектов. Вы можете свободно использовать addLocation() с near struct Location в каждом объекте так же, как и в исходной коллекции, но эта реализация остается за вами, поскольку она просто делает то же самое на вложенной основе.

Собрав вместе addLocation() функцию таким образом, который выглядит как то, что вы пытаетесь, вы можете сделать:

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
    /* realloc using temporary pointer adding 1 Location */
    void *tmp = realloc (l, (*nmemb + 1) * sizeof *l);  /* validate EVERY allocation */
    if (!tmp) {     /* on failure */
        perror ("error: realloc-l");
        return NULL;    /* original data good, that's why you realloc to a tmp */
    }

    /* on successful allocation */
    l = tmp;                                /* assign reallocated block to l */
    l[*nmemb].isValid = 1;                  /* assign remaining values and */
    l[*nmemb].name = name;                  /* allocate for near */
    l[*nmemb].near = calloc(nearCount, sizeof(Location));
    if (!l[*nmemb].near) {
        perror ("calloc-l[*nmemb].near");
        return NULL;
    }
    l[*nmemb].nearCount = nearCount;        /* set nearCount */
    (*nmemb)++;                             /* increment nmemb */

    return l;  /* return pointer to allocated block of Location */
}

Затем вы можете l oop заполнить каждую чем-то похожим на:

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
        if (!(l = addLocation (l, &i, names[i], nearCount)))
            break;

( примечание: i обновляется в addLocation, поэтому нет необходимости в i++ в вашем определении l oop)

Полный пример может быть написан следующим образом. Я добавил функцию печати и функцию удаления всей выделенной памяти. При вызове addLocation ниже вы увидите, что names[i%nnames] используется вместо names[i], а при использовании счетчика по модулю общее количество имен в моем списке просто гарантирует, что имя из списка предоставлено независимо от размера i.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

typedef struct Location {
    char isValid;
    char *name;
    struct Location *near;
    int nearCount;
} Location;

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
    /* realloc using temporary pointer adding 1 Location */
    void *tmp = realloc (l, (*nmemb + 1) * sizeof *l);  /* validate EVERY allocation */
    if (!tmp) {     /* on failure */
        perror ("error: realloc-l");
        return NULL;    /* original data good, that's why you realloc to a tmp */
    }

    /* on successful allocation */
    l = tmp;                                /* assign reallocated block to l */
    l[*nmemb].isValid = 1;                  /* assign remaining values and */
    l[*nmemb].name = name;                  /* allocate for near */
    l[*nmemb].near = calloc(nearCount, sizeof(Location));
    if (!l[*nmemb].near) {
        perror ("calloc-l[*nmemb].near");
        return NULL;
    }
    l[*nmemb].nearCount = nearCount;        /* set nearCount */
    (*nmemb)++;                             /* increment nmemb */

    return l;  /* return pointer to allocated block of Location */
}

void prn_locations (Location *l, size_t nmemb)
{
    for (size_t i = 0; i < nmemb; i++)
        if (l[i].isValid)
            printf ("%-12s    nearCount: %d\n", l[i].name, l[i].nearCount);
}

void del_all (Location *l, size_t nmemb)
{
    for (size_t i = 0; i < nmemb; i++)
        free (l[i].near);   /* free each structs allocated near member */

    free (l);   /* free all struct */
}

int main (int argc, char **argv) {

    char *endptr,   /* use with strtoul conversion, names below */
        *names[] = { "Mary", "Sarah", "Tom", "Jerry", "Clay", "Bruce" };
    size_t  nmemb = argc > 1 ? strtoul (argv[1], &endptr, 0) : 4,
            nnames = sizeof names / sizeof *names;
    int nearCount = 3;      /* set nearCourt */
    Location *l = NULL;     /* pointer to allocated object */

    if (errno || (nmemb == 0 && endptr == argv[1])) {   /* validate converstion */
        fputs ("error: nmemb conversion failed.\n", stderr);
        return 1;
    }

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
        if (!(l = addLocation (l, &i, names[i%nnames], nearCount)))
            break;

    prn_locations (l, nmemb);
    del_all (l, nmemb);
}

Пример использования / Вывод

$ ./bin/locationalloc
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3

Или, например, если вы хотите выделить для 10 из них затем:

$ ./bin/locationalloc 10
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3
Clay            nearCount: 3
Bruce           nearCount: 3
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняют указатель на начальный адрес для блока памяти, поэтому (2) он может быть освобожден , когда его нет требуется дольше.

Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированное значение и, наконец, что вы освобождаете всю выделенную память.

Для Linux valgrind это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind ./bin/locationalloc
==13644== Memcheck, a memory error detector
==13644== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13644== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13644== Command: ./bin/locationalloc
==13644==
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3
==13644==
==13644== HEAP SUMMARY:
==13644==     in use at exit: 0 bytes in 0 blocks
==13644==   total heap usage: 9 allocs, 9 frees, 1,728 bytes allocated
==13644==
==13644== All heap blocks were freed -- no leaks are possible
==13644==
==13644== For counts of detected and suppressed errors, rerun with: -v
==13644== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Позвольте мне знать, соответствует ли это вашим намерениям и есть ли у вас дополнительные вопросы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...