Определение двух указателей в C -программировании для динамического c выделения массива - PullRequest
0 голосов
/ 09 февраля 2020

(правка) Может кто-нибудь помочь мне понять, почему выделение динамического c массива основано на двух указателях (этот код работает без проблем)

int main(int argc, char** argv) {
    int i;
    int n=3;
    int* *t1;
    *t1 = (int*) malloc(n*sizeof(int));
    for (i = 0 ; i <n ;  i++) 
    {
            scanf("%d",(*t1+i));
    }
    for ( i=0; i<n; i++)
    {
        printf("%d",*(*t1+i));
    }

, в то время как единственная запись, которую я знаю, что это

int* t;
t = (int*) malloc(n*sizeof(int)); 

, так каков интерес использования * t вместо t . Спасибо.

Ответы [ 4 ]

1 голос
/ 09 февраля 2020

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

Чтобы понять динамическое распределение c, вы должны понимать, что массивов нет вовлечено (исключение выделяется для указателя на массив фиксированного размера).

В противном случае динамическое выделение c включает выделение блока памяти заданного размера и назначение начального адреса для этого нового блока памяти указателю. В C нет необходимости разыгрывать возврат malloc, это не нужно. См .: Должен ли я привести результат mallo c? (в C ++ это необходимо)

Выделение памяти для целых чисел

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

int *t;

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

int n = 100;
t = malloc (n * sizeof *t);

malloc выделяет новый блок памяти размером n * sizeof *t байтов, размер которого достаточен для хранения n целочисленных значений, а начальный адрес этого блока назначается указателю t. Использование разыменованного sizeof *t для установки type-size обеспечивает правильный размер шрифта. (t является указателем на int, когда вы разыменовываете t (например, *t), ваш тип просто старый int, поэтому sizeof *t эквивалентно sizeof (int))

Может показаться, что это не имеет значения сейчас (и для тривиальных типов это не имеет большого значения), но по мере того, как типы объектов, с которыми вы имеете дело, становятся сложнее и уровни косвенности указателей ошибочная установка неправильного размера шрифта становится распространенной проблемой при работе с выделенными вложенными структурами и т. д. c. Поэтому рекомендуется просто использовать разыменованный указатель для установки размера шрифта.

Давайте возьмем Простой пример, который использует указатель int *t_1D;, чтобы подчеркнуть, что это одиночное линейное распределение, которое можно индексировать как простой одномерный массив:

#define NUMINT 50       /* number of integers for t_1D */
...
int main (void) {

    int *t_1D = NULL;               /* pointer to int */

    t_1D = malloc (NUMINT * sizeof *t_1D);  /* allocate storage for NUMINT int */

    if (t_1D == NULL) {             /* validate EVERY allocation, handle error */
        perror ("malloc-t_1D");
        return 1;
    }

    for (int i = 0; i < NUMINT; i++)
        t_1D[i] = i + 1;                /* assign a value to each integer */

    puts ("\nvalues in t_1D (with \\n every 5th integer)\n");
    for (int i = 0; i < NUMINT; i++) {  /* loop over each value */
        if (i && i % ARRSZ == 0)        /* simple mod test to output newline */
            putchar ('\n');
        printf (" %3d", t_1D[i]);       /* output value at address */
    }
    puts ("\n");

    free (t_1D);                    /* don't forget to free what you allocate */

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

Имитация 2D-массива с указателем на указатель

Если вы используете указатель на указатель (например, int **t;) в качестве вашего указателя, вы выделяете двухэтапный процесс . Сначала вы выделяете блок памяти для хранения некоторого количества указателей (которое вы обычно считаете числом rows для объекта, который вы выделяете).

Затем во втором наборе выделений вы выделите блок памяти для хранения количества целых чисел на строку, которое вы будете sh хранить для каждой строки (обычно это col (столбец) число целых чисел) и присвойте начальный адрес для каждого выделенного блока одному из выделенных указателей - последовательно.

Результатом является структура данных, в которой вы выделяете 1 блок памяти для хранения row числовых указателей и row блоков памяти для хранения col количества целых чисел. Затем вы можете получить доступ к каждой ячейке памяти так же, как и к значениям в двумерном массиве.

Однако обратите внимание, поскольку для создания объекта из указателя на указатель требуется несколько выделений, потребуется несколько вызовов free, необходимых для освобождения памяти, выделенной для объекта.

Теперь давайте посмотрим, как это можно использовать. Ниже мы будем использовать int **t_2D; в качестве имени переменной, чтобы указать, что мы храним объект, который мы можем индексировать как двумерный массив:

#define ARRSZ   5       /* number of integers per-row for t_2D and t_PTA */
...
    int **t_2D = NULL;              /* pointer to pointer to int */
    ...
    /* a pointer to pointer (allocate pointers, then ints for each pointer) */

    t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D);    /* allocate 10 pointers */

    if (!t_2D) {                    /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)
        /* allocate/validate storage for 5 int assign start address to each ptr */
        if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) {    /* on failure */
            while (i--)             /* free previously allocated block for int */
                free (t_2D[i]);
            free (t_2D);            /* free pointers */
            return 1;
        }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_2D[i][j] = (i * ARRSZ) + j + 1;   /* assign value for each int */

    puts ("values in t_2D output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_2D[i][j]);        /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        free (t_2D[i]);                         /* free integers */
    free (t_2D);                                /* free pointers */

Обратите внимание на l oop в конце, который зацикливается на каждом указатель освобождает память для целых чисел перед вызовом free, чтобы освободить блок памяти, содержащий сами указатели.

A Pointer-To-Array - одиночное распределение, одиночное освобождение, двумерное индексирование

Существует еще одно распространенное распределение, которое вы встретите с этим объяснением. Вы можете использовать указатель на массив фиксированной длины (например, int (*t_PTA)[CONST];), а затем выделять память для некоторого количества фиксированных массивов в одном вызове и иметь возможность обращаться к объекту как 2D-массив как было сделано выше с t_2D. Поскольку требуется только одно выделение, для освобождения памяти, связанной с объектом, требуется только одно освобождение.

( примечание: не путайте пуитнер -в массиве (например, int (*p)[CONST]) с массивом указателей (например, int *p[CONST]), они являются двумя различными типами)

Для выделения, использования и освободив объект, созданный из указателя на массив , вы можете сделать следующее:

    /* a pointer to array -- single allocation, single-free, 2D indexing */

    int (*t_PTA)[ARRSZ] = NULL;     /* pointer to array of int[ARRSZ] */

    t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA);  /* storage for 50 integers */

    if (!t_PTA) {                   /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_PTA[i][j] = (i * ARRSZ) + j + 1;  /* assign value for each int */

    puts ("values in t_PTA output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_PTA[i][j]);       /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    free (t_PTA);    

( примечание: удобство одного free (t_PTA); используется для освобождения всей памяти, связанной с объектом.)

Теперь давайте рассмотрим все это в работоспособном примере:

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

#define ARRSZ   5       /* number of integers per-row for t_2D and t_PTA */
#define NUMINT 50       /* number of integers for t_1D */

int main (void) {

    int *t_1D = NULL,               /* pointer to int */
        **t_2D = NULL,              /* pointer to pointer to int */
        (*t_PTA)[ARRSZ] = NULL;     /* pointer to array of int[ARRSZ] */

    /* handling simple storage for integers */

    t_1D = malloc (NUMINT * sizeof *t_1D);  /* allocate storage for NUMINT int */

    if (t_1D == NULL) {             /* validate EVERY allocation, handle error */
        perror ("malloc-t_1D");
        return 1;
    }

    for (int i = 0; i < NUMINT; i++)
        t_1D[i] = i + 1;                /* assign a value to each integer */

    puts ("\nvalues in t_1D (with \\n every 5th integer)\n");
    for (int i = 0; i < NUMINT; i++) {  /* loop over each value */
        if (i && i % ARRSZ == 0)        /* simple mod test to output newline */
            putchar ('\n');
        printf (" %3d", t_1D[i]);       /* output value at address */
    }
    puts ("\n");

    free (t_1D);                    /* don't forget to free what you allocate */

    /* a pointer to pointer (allocate pointers, then ints for each pointer) */

    t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D);    /* allocate 10 pointers */

    if (!t_2D) {                    /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)
        /* allocate/validate storage for 5 int assign start address to each ptr */
        if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) {    /* on failure */
            while (i--)             /* free previously allocated block for int */
                free (t_2D[i]);
            free (t_2D);            /* free pointers */
            return 1;
        }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_2D[i][j] = (i * ARRSZ) + j + 1;   /* assign value for each int */

    puts ("values in t_2D output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_2D[i][j]);        /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        free (t_2D[i]);                         /* free integers */
    free (t_2D);                                /* free pointers */

    /* a pointer to array -- single allocation, single-free, 2D indexing */

    t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA);  /* storage for 50 integers */

    if (!t_PTA) {                   /* validate EVERY allocation */
        perror ("malloc-t_2D");
        return 1;
    }

    for (int i = 0; i < NUMINT/ARRSZ; i++)      /* loop over pointers */
        for (int j = 0; j < ARRSZ; j++)         /* loop over integer storage */
            t_PTA[i][j] = (i * ARRSZ) + j + 1;  /* assign value for each int */

    puts ("values in t_PTA output by row x col:\n");
    for (int i = 0; i < NUMINT/ARRSZ; i++) {
        for (int j = 0; j < ARRSZ; j++)
            printf (" %3d", t_PTA[i][j]);       /* output each integer */
        putchar ('\n');
    }
    putchar ('\n');

    free (t_PTA);    

}

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

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

$ ./bin/dynalloc_1_2_pta

values in t_1D (with \n every 5th integer)

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_2D output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_PTA output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

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

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

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

$ valgrind ./bin/dynalloc_1_2_pta
==10642== Memcheck, a memory error detector
==10642== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10642== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10642== Command: ./bin/dynalloc_1_2_pta
==10642==

values in t_1D (with \n every 5th integer)

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_2D output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

values in t_PTA output by row x col:

   1   2   3   4   5
   6   7   8   9  10
  11  12  13  14  15
  16  17  18  19  20
  21  22  23  24  25
  26  27  28  29  30
  31  32  33  34  35
  36  37  38  39  40
  41  42  43  44  45
  46  47  48  49  50

==10642==
==10642== HEAP SUMMARY:
==10642==     in use at exit: 0 bytes in 0 blocks
==10642==   total heap usage: 14 allocs, 14 frees, 1,704 bytes allocated
==10642==
==10642== All heap blocks were freed -- no leaks are possible
==10642==
==10642== For counts of detected and suppressed errors, rerun with: -v
==10642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Это много переварить за один присест, но возьмите его по частям, и динамическое распределение c начнет обретать смысл. Дайте мне знать, если у вас есть дополнительные вопросы.

1 голос
/ 09 февраля 2020

В первом случае t - указатель на int*. И правильный путь был бы для выделения:

int **t;
t = (int**) malloc(n*sizeof(int*));

Другими словами: это массив int*, который является динамически распределенной версией int* t[n].

И это отличается от второго случая, где t - указатель на тип int, в основном t - это массив int в этом случае, который похож на динамически размещенную версию int t[n].

0 голосов
/ 09 февраля 2020

int** будет использоваться, если вы хотите выделить блок из int* объектов. В вашем примере это не то, что вы сделали или действительно хотите. На самом деле вы ничего не выделяли или не назначали для t1, что делало выделение для *t1 (т.е. t1[0]) недопустимым, поскольку t1 само по себе неизвестно.

Если, например, вы хотели выделить массив указателей на несколько выделенных int массивов, тогда int** будет подходящим типом данных для массива указателей на массивы int:

// Allocate n arrays of m ints
int** t = malloc( sizeof(int*) * n ) ;
for( int i = 0; i < n; i++ )
{
    t[i] = malloc( sizeof(int) * m ) ;
} 

Тогда, например, t[2][3] относится к четвертый элемент в третьем блоке int.

Это не проблема синтаксиса; они не разные формы одного и того же; они семантически разные, и в контексте вашего примера int** неуместно и семантически неверно.

0 голосов
/ 09 февраля 2020

Пожалуйста, уточните. Однако первая версия неверна. Если вы хотите выделить массив указателей для массивов, вы должны сделать:

int **t;
t=(int**) malloc(n*sizeof(int*));

Чтобы выделить массив указателей для каждой отдельной строки, а затем выделить строки:

for (int i=0; i<n; ++i){
  t[i] = (int*) malloc(n*sizeof(int));
}

*t не будет действительным в вашем первом фрагменте, так как к нему можно получить доступ. Сначала вы должны выделить **t.

...