Определение двух указателей в 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 ответа


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

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

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

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

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

int *t;

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

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

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

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

Давайте рассмотрим простой пример, в котором пользователи используют указатель 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 ("
values in t_1D (with \n every 5th integer)
");
    for (int i = 0; i < NUMINT; i++) {  /* loop over each value */
        if (i && i % ARRSZ == 0)        /* simple mod test to output newline */
            putchar ('
');
        printf (" %3d", t_1D[i]);       /* output value at address */
    }
    puts ("
");

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

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

Имитация 2D массива с помощью указателя на указатель

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

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

В результате получается структура данных, в которой вы выделяете 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:
");
    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 ('
');
    }
    putchar ('
');

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

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

Указатель на массив - одиночное распределение, одиночное свободное двумерное индексирование

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

( примечание: не путайте poitner-to-array (например, 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:
");
    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 ('
');
    }
    putchar ('
');

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

    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:
");
    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 ('
');
    }
    putchar ('
');

    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:
");
    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 ('
');
    }
    putchar ('
');

    free (t_PTA);    

}

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

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

$ ./bin/dynalloc_1_2_pta

values in t_1D (with 
 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 
 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)

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

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


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

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

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

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


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

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

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

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

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


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** неуместен и семантически неверен.


Есть идеи?

10000