проблема доступа к распределению памяти по двойному указателю с арифметикой указателя без индекса - PullRequest
0 голосов
/ 14 июня 2019

Я пытаюсь использовать двойной указатель выделения памяти без использования индекса. Мне разрешено использовать только арифметику указателей для доступа к адресу.

char** p2 = (char**) malloc(sizeof(char) * 2);
*p2 = (char*)malloc(sizeof(char) * 3);


**p2 = 's';
printf("%c\n", **p2);
printf("%p\n", **p2);
printf("%c\n", p2[0][0]);
printf("%p\n\n", p2[0][0]);

(**p2)++;
**p2 = 'k';
printf("%c\n", **p2);
printf("%p\n", **p2);
printf("%c\n", p2[0][1]);
printf("%p\n\n", p2[0][1]);

After fail above code,

int* ptr = *p2;
*ptr = 'h';
printf("%c\n", *ptr);
printf("%i\n", *ptr);
printf("%c\n", p2[0][0]);
printf("%i\n\n", p2[0][0]);

(*ptr)++;
*ptr = 'a';
printf("%c\n", *ptr);
printf("%i\n", *ptr);
printf("%c\n", p2[0][1]);
printf("%i\n\n", p2[0][1]);

этот тоже не сработал.

Итак, я смог получить доступ к til p2 [0] [0], вернув s и 0x73.

Однако, когда я попытался получить доступ к следующему индексу, используя арифметику указателей (** p ++), ** p2 value и p2 [0] [1] отличаются.

Какую арифметику указателей я должен использовать для доступа к p2 [0] [1] и другим?

1 Ответ

0 голосов
/ 14 июня 2019

Указатели относительно прямолинейны, но в вашем случае кажется, что вам трудно понять «Каков мой указатель?» Чтение между заварками чуть дальше, похоже, что ваша основная путаница окружает «Что это делает с '*' с?»

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

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

Это указатели в двух словах. Дайте мне знать, если у вас есть дополнительные вопросы.

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