2017-01-18 8 views
2

Этот вопрос был дан ответ here, но для меня некоторые вещи все еще остаются открытыми, и обсуждение слишком старое, чтобы кто-либо мог ответить на комментарии, кроме того, я хотел спросить, что было не так в моей реализация.Выделите пространство для 3-мерного массива в C

В любом случае проблема возникает. Я хочу выделить пространство для 3D-массива измерений 3, h, w. Что я делаю:

int ***a = (int***)malloc(3 * sizeof(int*)); 
for (int i = 0; i < 3; i++) 
{ 
    a[i] = (int **) malloc(height * sizeof(int*)); 
} 

Я понимаю это как создание первого места для трех 3 int**, а затем распределение высоты int**; Тогда:

for (int i = 0; i < 3; i++) 
{ 
    a[i][0] = (int *) malloc(height * width * sizeof(int *)); 
    for (int j = 1; j < height; j++) 
    { 
    a[i][j] = a[i][j-1] + width; 
    } 
} 

Так что теперь я думаю, что каждый a[i][0] в основном пространство для 2D массива высоты и ширины размеров; кроме того, остальная часть a[i][j] для j != 0 инициализируется рядом j th: a[i][0];

я инициализировать значения с помощью:

for (int c = 0; c < 3; c++){ 
    for (int i = 0; i < height; i++){ 
    for (int j = 0; j < width; j++){ 
     a[c][i][j] = i+j+c; 

Однако это неверно. Я хотел бы сделать это в качестве расширения упражнения для выделения 2D массива следующим образом:

int *a[height]; 
a[0] = (int*)malloc(height * width * sizeof(int)); 
for (int i = 1; i < height; i++){ 
    a[i] = a[i-1] + widht; 
} 

Этого путь является массивом указателей высоты длины, a[0] выделяются для всей 2D массива и последующие указатели укажите их соответствующие строки, и я могу написать им просто, позвонив по телефону a[i][j] = some number;

Что я хочу сделать, это расширить эту двумерную идею до 3D-футляра, но я, к сожалению, терпит неудачу.

+0

'int (* a) [height] [width] = malloc (3 * sizeof * a);' должен предоставить вам 3D-массив. – mch

+0

не создаст ли указатель на 2D-массив высот, ширины? –

+0

yes, 'a' является указателем на 2d-массив и может использоваться с' a [i] [j] [k] '. – mch

ответ

2

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

int ***   int **    int *     int 

    +---+   +---+     +---+     +---+ 
a | | ---> a[0] | | ------> a[0][0] | | ----> a[0][0][0] | | 
    +---+   +---+     +---+     +---+ 
      a[1] | | ----+ a[0][1] | | -+ a[0][0][1] | | 
        +---+  |   +---+ |    +---+ 
      a[2] | |  |   ... |    ... 
        +---+  |   +---+ |    +---+ 
          | a[0][h-1] | | | a[0][0][w-1] | | 
          |   +---+ |    +---+ 
          |     | 
          |   +---+ |    +---+ 
          +-> a[1][0] | | +--> a[0][1][0] | | 
             +---+     +---+ 
           a[1][1] | |  a[0][1][1] | | 
             +---+     +---+ 
             ...     ... 
             +---+     +---+ 
           a[1][h-1] | |  a[0][1][w-1] | | 
             +---+     +---+ 

Типы:

  • Каждый a[i][j][k] имеет тип int ;
  • Каждый a[i][j] указывает на первый элемент последовательности объектов int, поэтому он должен иметь тип int *;
  • Каждый a[i] указывает на первый элемент последовательности int * объектов, поэтому он должен иметь тип int **;
  • a указывает на первый элемент последовательности int ** объектов, поэтому он должен иметь тип int ***.

Поскольку вы делаете фрагментарное вложенное выделение, вам нужно проверить результат каждого malloc вызова, и если есть ошибка, вы хотите, чтобы очистить все ранее выделенную память до выручая, в противном случае вы рискуете утечки памяти. К сожалению, нет действительно чистого или элегантного способа сделать это - вы либо несете флаг и делаете кучу дополнительных тестов, либо бросаете пару goto с. Я собираюсь показать два разных подхода, ни один из которых не хорош.

Первый метод - как мы выделяем каждый a[i], мы также выделяем каждый a[i][j] (подход «глубина») и инициализируем каждый a[i][j][k]. Если распределение по a[i][j] терпит неудачу, мы должны освободить все a[i][0] через a[i][j-1], а затем освободить a[i], затем повторите процесс для каждого из a[0] через a[i-1]:

/** 
* Depth-first approach: allocate each a[i][j] with each a[i] 
*/ 
int ***alloc1(size_t pages, size_t height, size_t width) 
{ 
    size_t i, j, k; 

    int ***a = malloc(sizeof *a * pages); // allocate space for N int ** 
              // objects, where N == pages 

    if (!a) 
    return NULL; 

    for (i = 0; i < pages; i++) 
    { 
    a[i] = malloc(sizeof *a[i] * height); // for each a[i], allocate space for 
    if (!a[i])        // N int * objects, where N == height 
     goto cleanup_1; 

    for (j = 0; j < height; j++) 
    { 
     a[i][j] = malloc(sizeof *a[i][j] * width); // for each a[i][j], allocate  
     if (!a[i][j])        // space for N int objects, 
     goto cleanup_2;       // where N == w 

     for (k = 0; k < width; k++) 
     a[i][j][k] = initial_value(i, j, k); 
    } 
    } 
    goto done; 

/** 
* Free all of a[i][0] through a[i][j-1], then free a[i] 
*/ 
cleanup_2: 
    while (j--) 
    free(a[i][j]); 
    free(a[i]); 

/** 
* Free all of a[0] through a[i-1], then free a 
*/ 
cleanup_1: 
    while (i--) 
    { 
    j = height; 
    goto cleanup_2; 
    } 
    free(a); 
    a = NULL; 

done: 
    return a; // at this point, a is either a valid pointer or NULL. 
} 

Да, этот код содержит goto с, и это нарушает одно правил моего питомца (никогда не отступайте назад). Но между кодом распределения и кодом очистки существует довольно четкое разделение, и мы не повторяемся в разделах очистки. cleanup_2 освобождает все строки внутри страницы вместе с самой страницей; cleanup_1 освобождает все страницы. cleanup_2 "проваливается" в cleanup_1.

Вот второй метод - сначала выделить всеa[i] перед выделением каких-либо a[i][j], то убедитесь, что все a[i][j] были успешно выделены перед инициализацией содержимого массива.

/** 
* Breadth-first approach; allocate all a[i], then all a[i][j], then initialize 
*/ 
int ***alloc2(size_t pages, size_t height, size_t width) 
{ 
    size_t i, j, k; 

    /** 
    * Allocate space for N int ** objects, where N == pages 
    */ 
    int ***a = malloc(sizeof *a * pages); 

    if (!a) 
    return NULL;      // allocation failed for initial sequence, return NULL 

    for (i = 0; i < pages; i++) // For each a[i], allocate N objects of type 
    {        // int *, where N == height 
    a[i] = malloc(sizeof *a[i] * height); 
    if (!a[i]) 
     break; 
    } 

    if (i < pages) 
    { 
    while (i--)      // allocation of a[i] failed, free up a[0] through a[i-1] 
     free(a[i]); 
    free(a);       // free a 
    return NULL; 
    } 

    for (i = 0; i < pages; i++)  
    { 
    for (j = 0; j < height; j++) 
    { 
     a[i][j] = malloc(sizeof *a[i][j] * width); // for each a[i][j], allocate  
     if (!a[i][j])        // space for N int objects, 
     break;         // where N == width 
    } 
    } 

    if (j < h) 
    { 
    do 
    { 
     while (j--)      // allocation of a[i][j] failed, free up a[i][0] through a[i][j-1] 
     free(a[i][j]);    // repeat for all of a[0] through a[i-1]. 
     free(a[i]); 
     j = h; 
    } while (i--); 
    free(a);       // free a 
    return NULL; 
    } 

    /** 
    * All allocations were successful, initialize array contents 
    */  
    for (i = 0; i < pages; i++) 
    for (j = 0; j < height; j++) 
     for (k = 0; k < width; k++) 
     a[i][j][k] = initial_value(i, j, k); 

    return a; 
} 

No goto s! Но код не «течет», а ИМО. Существует не так чистое разделение между кодом выделения и очистки, и между разделами очистки есть немного повторяемости.

Обратите внимание, что для обоих методов, память не смежный - элемент сразу после a[0][0][h-1] не будет a[0][1][0]. Если вам нужны все ваши элементы массива, чтобы быть смежными в памяти, вам нужно будет использовать метод, проявленный другие:

int (*a)[h][w] = malloc(sizeof *a * 3); 

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

+0

Обратите внимание, что на картинке не отображается 3D-массив, а несколько разбросанных сегментов, на которые указывает справочная таблица. Поэтому, если вам нужен 3D-массив, это неправильное решение. Кроме того, объединение трехзвездочного программирования с программированием спагетти может быть не лучшей идеей. Вместо спагетти gotos, вы можете просто инициализировать все указатели до NULL перед вызовом malloc. Таким образом, вы можете передать их в 'free()' независимо от того, указывают ли они на выделенную память или нет. Тогда требуется только одна процедура очистки. – Lundin

+0

@ Lundin: OP хотел, чтобы пример * несмежного * распределения был расширен до 3 измерений, что я и представил. –

+0

Я подозреваю, что вопрос о проблеме XY. OP спрашивает, как использовать таблицы поиска указателя для указателя для распределения динамической памяти. Хотя фактическая проблема, которую они пытаются решить, вероятно, просто «как распределить 3D-массив динамически». – Lundin

2
int ***a = (int***)malloc(3 * sizeof(int*)); 

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

Вместо этого выделить 3D массив:

int (*a)[y][z] = malloc(sizeof(int[x][y][z])); 

for(int i=0; i<x; i++) 
    for(int j=0; j<y; j++) 
    for(int k=0; k<z; k++) 
     a[i][j][k] = something; 

free(a); 

EDIT (любой саркастический тон ниже в тексте является полностью намеренным)

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

К сожалению, он еще не содержит условного разветвления вверх, утечки памяти или сложную арифметику указателя. Но для трехзвездочного программиста такие радостные вещи, конечно, тривиально добавить позже. Тем не менее, он гарантирует пропуски кэша данных, так что программа будет работать с равной производительностью, как система без кэша данных.

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

[Как и в случае с методом указателя на указатель, очень плохой практикой, но если вы по неизвестным причинам настаиваете на его использовании, вы также можете сделать это правильно.]

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


void free_int_3d (int*** ppp, size_t X, size_t Y, size_t Z) 
{ 
    (void)Z; 

    if(ppp == NULL) 
    { 
    return ; 
    } 

    for(size_t x=0; x < X; x++) 
    { 
    if(ppp[x] != NULL) 
    { 
     for(size_t y=0; y < Y; y++) 
     { 
     if(ppp[x][y] != NULL) 
     { 
      free(ppp[x][y]); 
     } 
     } 
     free(ppp[x]); 
    } 
    } 
    free(ppp); 
} 


#define malloc_with_cleanup(ptr, size)  \ 
ptr = malloc(size);      \ 
if(ptr == NULL)       \ 
{           \ 
    free_int_3d(result, X, Y, Z);   \ 
    return NULL;        \ 
} 

int*** int_alloc_3d (size_t X, size_t Y, size_t Z) 
{ 
    int*** result = NULL; 

    malloc_with_cleanup(result, sizeof(int**[X])); 
    for(size_t x=0; x<X; x++) 
    { 
    malloc_with_cleanup(result[x], sizeof(int*[Y])); 
    for(size_t y=0; y<Y; y++) 
    { 
     malloc_with_cleanup(result[x][y], sizeof(int[Z])); 
    } 
    } 
    return result; 
} 


int main (void) 
{ 
    const size_t X = 5; 
    const size_t Y = 3; 
    const size_t Z = 4; 

    int*** ppp = int_alloc_3d(X, Y, Z); 

    for(size_t i=0; i<X; i++) 
    { 
    for(size_t j=0; j<Y; j++) 
    { 
     for(size_t k=0; k<Z; k++) 
     { 
     ppp[i][j][k] = (int)k; 
     printf("%d ", ppp[i][j][k]); 
     } 
     printf("\n"); 
    } 
    printf("\n"); 
    } 

    free_int_3d(ppp, X, Y, Z); 

    return 0; 
} 

Выход:

0 1 2 3 
0 1 2 3 
0 1 2 3 

0 1 2 3 
0 1 2 3 
0 1 2 3 

0 1 2 3 
0 1 2 3 
0 1 2 3 

0 1 2 3 
0 1 2 3 
0 1 2 3 

0 1 2 3 
0 1 2 3 
0 1 2 3 
+0

Я знаю, что он не выделяет весь массив, что я хотел сделать, чтобы выделить 3 указателя типа 'int **' –

+0

@VahagnTumanyan Почему? Вы сказали, что хотите 3D-массив. В этом контексте указатели на указатели не имеют смысла. – Lundin

+0

Просто как упражнение для работы с несколькими указателями указателей любых размеров. Кроме того, используйте более правильный синтаксис (семантика?) Для 'malloc', как при отбрасывании возвращаемого' void * 'к правильному типу указателя и наличия' (number_of_elems * sizeof (type)) ', а не просто выделения массива , –

3

Для решения высшей цели OP, я подозреваю, что указатель на 2D массив будет достаточно @mch

int (*a)[height][width] = malloc(sizeof *a * 3); 

a[c][i][j] = i + j + c; 

Но согласно запросу OP .. В ..

... выделить место для 3D-массива ...

Как закодировать обычно создают 3D-массив?
В приведенном ниже коде используется массив переменной длины , доступный на C99.
(VLA необязательно поддержка в С11)

int a1[3][height][width]; 
// and assign it accordingly 
for (int c = 0; c < 3; c++) { 
    for (int i = 0; i < height; i++) { 
    for (int j = 0; j < width; j++) { 
     a1[c][i][j] = i + j + c; 

Но ОП хочет выделить пространства для такого массива.

malloc() возвращает указатель. Указатель на выделенную память. Поэтому нам нужно создать указатель на такой массив. Как только мы это сделаем, распределение и назначение элементов просты.

int (*a2)[3][height][width] = malloc(sizeof *a2); 

// 3 nested for loop as above 
    (*a2)[c][i][j] = i + j + c; 

Пример кода

void VT_3_dimensional_array(int height, int width) { 
    assert(height > 0 && width > 0); 

    int (*a2)[3][height][width] = malloc(sizeof *a2); 
    printf("%p %zu\n", (void*) a2, sizeof *a2); 
    if (a2 == NULL) { 
    perror("Out of memory"); 
    exit(EXIT_FAILURE); 
    } 
    for (int c = 0; c < 3; c++) { 
    for (int i = 0; i < height; i++) { 
     for (int j = 0; j < width; j++) { 
     (*a2)[c][i][j] = i + j + c; 
     } 
    } 
    } 

    // use a; 
    for (int c = 0; c < 3; c++) { 
    for (int i = 0; i < height; i++) { 
     putchar('('); 
     for (int j = 0; j < width; j++) { 
     printf(" %X", (*a2)[c][i][j]); 
     } 
     putchar(')'); 
    } 
    puts(""); 
    } 

    free(a2); 
} 

int main() { 
    VT_3_dimensional_array(5, 7); 
    return 0; 
} 

выход

0x80071980 420 
(0 1 2 3 4 5 6)(1 2 3 4 5 6 7)(2 3 4 5 6 7 8)(3 4 5 6 7 8 9)(4 5 6 7 8 9 A) 
(1 2 3 4 5 6 7)(2 3 4 5 6 7 8)(3 4 5 6 7 8 9)(4 5 6 7 8 9 A)(5 6 7 8 9 A B) 
(2 3 4 5 6 7 8)(3 4 5 6 7 8 9)(4 5 6 7 8 9 A)(5 6 7 8 9 A B)(6 7 8 9 A B C) 

Примечание, технически код OP является использование неправильного размера. Обычно sizeof(int*) будет таким же, как sizeof(int**), поэтому никакого большого вреда. Тем не менее, это указывает на путаницу в распределении.

// wrong type -----------------------v should be int** 
int ***a = (int***)malloc(3 * sizeof(int*)); 
+0

Интересно, кто это проигнорировал. Кажется совершенно законным для меня. Но я думаю, вы могли бы остановить этого спутника, переместив последний абзац на верх и приступите к объяснению позже. – cmaster

+0

@cmaster OK ответ исправлен. – chux

+1

Что получается с помощью указателя на 3d VLA и «malloc'ing space вместо того, чтобы просто объявить 3D VLA в первую очередь? Я полагаю, что массив 'malloc'ed может быть больше. –