Давайте начнем с базового 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)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Позвольте мне знать, соответствует ли это вашим намерениям и есть ли у вас дополнительные вопросы.