Указатели относительно прямолинейны, но в вашем случае кажется, что вам трудно понять «Каков мой указатель?» Чтение между заварками чуть дальше, похоже, что ваша основная путаница окружает «Что это делает с '*'
с?»
Давайте сделаем шаг назад и вспомним, что указатель - это просто обычная переменная, которая содержитадрес чего-то другого в качестве его значения.(то есть указывает на то, где что-то еще хранится в памяти).Хотя вы обычно думаете о int a = 5;
, где a
само содержит 5
в качестве значения, указатель int *b = &a;
содержит (указывает на) адрес a
в качестве значения (то есть b
указывает на адресв памяти, где хранится 5
).
Теперь давайте подумаем о том, как мы сохраняем объявления и использование указателей прямыми.Когда вы объявляете указатель, например, char **p2;
, вы объявляете указатель на указатель на char
.Поэтому, когда вы думаете о том, что вы можете присвоить p2
, вы просто отвечаете на вопросы (1) Каков мой указатель?(p2
- это указатель на: что? указатель на char
) и (2) Какой тип адреса сохраняет в качестве своегозначение?(p2
содержит указатель на char
) в качестве значения.
Когда вы объявили p2
и попытались выделить хранилище, какой тип вы использовали?sizeof(char)
.Теперь должно быть ясно, что p2
не содержит адрес char
в качестве значения, он содержит адрес указателя на char
в качестве значения, например (char*)
.
Способ получить это право каждый раз - использовать разыменованный указатель в качестве type-size вместо того, чтобы пытаться использовать базовый тип char, short, int, etc..
, а затемдобавьте '*'
s к нему в надежде сделать это правильно.Поскольку вы одновременно объявляете и инициализируете вызовом malloc
в один прием, вы маскируете простоту, располагая часть объявления **
в той же строке, что и выделение.Вместо этого подумайте об этом, чтобы начать разбираться в уме.
char **p2; /* declare a pointer-to-pointer-char */
p2 = malloc (sizeof *p2 * 2); /* use the dereferenced pointer to set typesize */
p2
- ваш указатель, поэтому *p2
задает ваш типизацию.(например, p2
- это указатель на указатель на char
), поэтому, когда вы разыменовываете его (т. е. удаляете один уровень косвенности), вы получаете указатель до char
).Что именно вам нужно выделить (указатели).Таким образом, sizeof *p2 * 2
выделяет блок памяти, достаточно большой для хранения двух указателей на char
.Нет никакого предположения о том, сколько '*'
s вы добавляете к char
, вы просто используете разыменованный указатель - и компилятор автоматически знает, сколько памяти требует тип.Затем p2 = malloc ....
назначает начальный адрес для этого блока памяти на p2
.
( примечание: вы должны подтвердить , что вызову malloc
удалось проверить возврат, например, if (p2 == NULL)
распределение не удалось.)
Вот и все.Типоразмер управляет арифметикой указателя.Поэтому, если у вас есть указатель на char
, (скажем, char *p;
), p++;
продвигает указатель на 1 байт до следующего символа .Если у вас есть указатель на int
(скажем, int *pi;
), то pi++;
увеличивает указатель на 4 байта, чтобы он указывал на следующее целое число .
Счто в качестве фона то, что вы пытаетесь сделать, похоже на следующий короткий пример.(примечание: я изменил формат вывода, чтобы его было проще читать)
#include <stdio.h>
#include <stdlib.h>
int main (void) {
/* don't cast the return of malloc,
* use dereferenced pointer as typesize.
* allocates 2 pointers, p2[0], p2[1] - unused.
*/
char **p2 = malloc (sizeof *p2 * 2);
if (!p2) { /* validate every allocation */
perror ("malloc-p2");
return 1;
}
/* use p2[0] instead of *(p2 + 0), i.e. *p2
* allocates 3-chars of storage, assigns starting address to p2[0].
*/
p2[0] = malloc (sizeof *p2[0] * 3);
if (!p2[0]) { /* ditto */
perror ("malloc-p2[0]");
return 1;
}
**p2 = 's'; /* assigns 's' to first char of first pointer */
/* you can't advance p2 or you lose your original pointer (memory-leak) */
char *ptmp = p2[0]; /* use a temp pointer instead to 1st 3-chars */
ptmp++; /* advance the temp pointer */
*ptmp = 'k'; /* assign 'k' as 2nd char */
printf (" p2 (%p)\n %c (%p)\n %c (%p)\n",
(void*)p2, p2[0][0], (void*)&p2[0][0], p2[0][1], (void*)&p2[0][1]);
free (p2[0]); /* free storage */
free (p2); /* free pointers */
}
( примечание: адрес p2
изменить нельзя. Если вы назначите что-то еще для p2
или advance p2++;
он больше не указывает на начало выделенного блока памяти, поэтому (если вы не сохраните копию указателя) блок памяти больше не может быть освобожден, что приведет к утечке памяти .)
Пример использования / Вывод
$ ./bin/ptr2ptrfun
p2 (0x73a010)
s (0x73a030)
k (0x73a031)
Вы видите выше p2
- указатель на блок памяти для 2-х указателей, 0x73a010
, в то время как адреса, в которых хранятся 's'
и 'k'
, являются последовательными и находятся в памяти для созданных 3-х символов при втором вызове malloc
с начальным адресом, назначенным первому выделенному указателю (второй выделенный указатель остается неиспользованным)
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/ptr2ptrfun
==8767== Memcheck, a memory error detector
==8767== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8767== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==8767== Command: ./bin/ptr2ptrfun
==8767==
p2 (0x51db040)
s (0x51db090)
k (0x51db091)
==8767==
==8767== HEAP SUMMARY:
==8767== in use at exit: 0 bytes in 0 blocks
==8767== total heap usage: 2 allocs, 2 frees, 19 bytes allocated
==8767==
==8767== All heap blocks were freed -- no leaks are possible
==8767==
==8767== For counts of detected and suppressed errors, rerun with: -v
==8767== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Это указатели в двух словах. Дайте мне знать, если у вас есть дополнительные вопросы.