在C语言中的内存分配 - 3D数组

4
我希望为C语言中的数据立方体分配内存,也就是说,我需要分配一个三维数组。然而,我的代码却返回“分段错误”,我不知道为什么。
我认为我的循环是正确的,但事实上,我的代码并不能正常工作。
这是我的代码:
int malloc3dfloat(float ****array, int q, int r, int s) {
    // allocate the q*r*s contiguous items 
    float *p = (float *) malloc(q*r*s*sizeof(float));
    if (!p) return -1;

    // allocate the row pointers into the memory 
    (*array) = (float ***) malloc(q*sizeof(float**));
    if (!(*array)) {
       free(p);
       return -1;
    }
    for (int i=0; i<q; i++)
    {
        (*array)[i] = (float **) malloc(r*sizeof(float*));
        if (!(*array[i])) 
        {
        free(p);
        return -1;
        }
    }

    //set up the pointers into the contiguous memory
    for (int i=0; i<q; i++)
    {
        for (int j=0; j<r; j++)
        { 
            (*array)[i][j] = &(p[(i*r+j)*s]);
        }
    }

    return 0;
}

3
在进行乘法运算时,应该始终将 sizeof 放在最前面。您可能会在乘法运算中溢出 int。如果使用 float *p = malloc(sizeof(*p) * q * r * s); ,则乘法运算将在 size_t 上执行,这样就不会溢出了。 - alx - recommends codidact
3
不要对malloc()的结果进行类型转换,并避免使用sizeof(type)。详情请参见https://dev59.com/dHRB5IYBdhLWcg3wgHWr#605858。 - alx - recommends codidact
3
这个代码中的 if (!(*array[i])) 应该改为 if (!((*array)[i])) - H.S.
1
我个人会这样做:float *p = malloc(q * r * s * sizeof *p);,然后通过 p[(x) + (y * q) + (z * q * r)] 这些坐标访问立方体。最后一个表达式中的括号并不是必须的,但是为了清晰起见加上比较好。 - Cheatah
// 将行指针分配到内存中 <<-- 您不需要预先计算和存储这些指针。它们可以在需要时计算。这也将消除成为三星级程序员的需要。 - wildplasser
显示剩余2条评论
5个回答

4

只需使用具有动态存储的变长数组即可。

float (*array)[r][s]=calloc(q, sizeof *array);

就这些啦!

现在使用 array[i][j][k] 语法来访问单独的元素。


很好,非常接近分配真正的3D数组了。 - chux - Reinstate Monica
不错,除非你需要将它们放入结构体或从函数中返回。 - Costantino Grana
2
@CostantinoGrana,是的。这很棘手。然而,有一个解决方法。我们可以将指向VLA的指针强制转换为void*,并在需要时重新转换回来。 - tstanisl
@tstanisl 更加简单:hhttps://dev59.com/questions/hlgQ5IYBdhLWcg3w3Xwy#42094467 寻找 void arr_alloc (size_t x, size_t y, int(**aptr)[x][y]) - Andrew Henle
1
@AndrewHenle,这正是我通常做的事情 :)。然后,知道尺寸后,我将此void*重新转换为有用的东西。 - tstanisl
显示剩余2条评论

3

int vs. size_t 数学计算

int q, int r, int s
...

//                   v---v Product calculated using int math
// float *p = malloc(q*r*s*sizeof(float));

// Less chance of overflow
float *p = malloc(sizeof (float) * q*r*s);
// or even better 
float *p = malloc(sizeof *p * q*r*s);
//                        ^-------^ Product calculated using wider of size_t and int math

OP 的 malloc3dfloat() 函数既不是分配真正的三维数组,也不是分配不规则的数组,而是两者的混合体。

如果想要分配不规则数组:

 // Full out-of-memory handling omitted for brevity
 int malloc3dfloat_j(float ****array, int q, int r, int s) {
   float ***a = malloc(sizeof *a * q);
   if (a == NULL) ...
   
   for (int qi = 0; qi < q; qi++) {  
     a[qi] = malloc(sizeof a[qi][0] * r);
     if (a[qi] == NULL) ...
  
     for (int ri = 0; ri < r; ri++) {  
       a[qi][ri] = malloc(sizeof a[qi][ri][0] * s);
       if (a[qi][ri] == NULL) ...
     }
   }

   *array = a;
   return 0;
 }

1
完全符合我的想法:使用本地变量并保持一致。 - Costantino Grana

2

我想到了与@tstanisl发布的解决方案类似的解决方法。我以前从未尝试过这样做,所以我对如何使其工作有些怀疑,因此我编写了一个简单的程序来展示它:

$ cat ap.c 
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE(a)   (sizeof((a)) / sizeof((a)[0]))
#define ARRAY_SSIZE(a)  ((ptrdiff_t) ARRAY_SIZE(a))

int main(void)
{
    int (*ap)[2][3][5];
    int l = 0;

    ap = malloc(sizeof(*ap));

    printf("%zu\n", sizeof(*ap));

    for (ptrdiff_t i = 0; i < ARRAY_SSIZE(*ap); ++i) {
        for (ptrdiff_t j = 0; j < ARRAY_SSIZE((*ap)[0]); ++j) {
            for (ptrdiff_t k = 0; k < ARRAY_SSIZE((*ap)[0][0]); ++k) {
                (*ap)[i][j][k] = l++;
            }
        }
    }

    for (ptrdiff_t i = 0; i < ARRAY_SSIZE(*ap); ++i) {
        for (ptrdiff_t j = 0; j < ARRAY_SSIZE((*ap)[0]); ++j) {
            for (ptrdiff_t k = 0; k < ARRAY_SSIZE((*ap)[0][0]); ++k)
                printf("%3i", (*ap)[i][j][k]);
            putchar('\n');
        }
        putchar('\n');
    }
}

$ ./a.out 
120
  0  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

我希望这对你有所帮助 :-)

我进行了进一步的测试,以检查是否存在未定义的行为,并检查我访问的地址是否是连续的,但我在这里删除了它们以简化代码。


编辑:

我的解决方案与@tstanisl的略有不同。以下是他建议的内容。使用您喜欢的那个。两者都很好。 这个更类似于函数中数组衰减为其第一个元素的指针。

$ cat ap.c 
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE(a)   (sizeof((a)) / sizeof((a)[0]))
#define ARRAY_SSIZE(a)  ((ptrdiff_t) ARRAY_SIZE(a))

int main(void)
{
    int (*ap/*[2]*/)[3][5];
    int l = 0;

    ap = malloc(sizeof(*ap) * 2);

    printf("%zu\n", sizeof(*ap) * 2);

    for (ptrdiff_t i = 0; i < 2; ++i) {
        for (ptrdiff_t j = 0; j < ARRAY_SSIZE(ap[0]); ++j) {
            for (ptrdiff_t k = 0; k < ARRAY_SSIZE(ap[0][0]); ++k) {
                ap[i][j][k] = l++;
            }
        }
    }

    for (ptrdiff_t i = 0; i < 2; ++i) {
        for (ptrdiff_t j = 0; j < ARRAY_SSIZE(ap[0]); ++j) {
            for (ptrdiff_t k = 0; k < ARRAY_SSIZE(ap[0][0]); ++k)
                printf("%3i", ap[i][j][k]);
            putchar('\n');
        }
        putchar('\n');
    }
}

$ ./a.out 
120
  0  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


0

没注意到你在帖子中想要连续的内存!!如果有用的话,这里是一些代码来创建一个3D表(并擦除它):

int malloc3dfloatBIS(float ****array, int d1, int d2, int d3) {
if (!(*array = malloc(sizeof(array) * d3)))
return -1;
for (int i = 0; i < d2 ; ++i)
    if (!((*array)[i] = malloc(sizeof(array) * d2))) {
        while (i)
            free ((*array)[i--]);
        return -1;
    }
for (int i = 0; i < d2 ; ++i)
    for (int j = 0; j < d1 ; ++j){
        if (!((*array)[i][j] = malloc(sizeof(****array) * d1))){
            for (;i;--i){
                while (j){
                    free    ((*array)[i][--j]);
                }
                j = d1; 
                free ((*array)[i]);
            }
            return -1;
        }
    }
return 0;
}

void erase(float ****array, int d1, int d2, int d3) {
    for (int i = d2;i ; --i){
        int j = d1;
        while (j)
            free ((*array)[i -1][--j]);
        free ((*array)[i - 1]);
    }
    free (*array); 
}

0
H.S.已经指出了你的错误。但其他人正确地建议只使用一个指针并避免预计算。然后你可以转向VLAs。我仍然喜欢chux版本,但为了有正确的malloc()失败管理,你可以这样做:
#include <stdio.h>
#include <stdlib.h>

// Full out-of-memory handling included
int malloc3dfloat(float ****array, int q, int r, int s) 
{
    char *p = malloc((sizeof(float**) + (sizeof(float*) + sizeof(float) * s) * r) * q);
    if (p == NULL) {
        return -1;
    }
    float ***pq = (void*)(p);
    float **pr = (void*)(p + sizeof(float**) * q);
    float *ps = (void*)(p + (sizeof(float**) + sizeof(float*) * r) * q);
    for (int qi = 0; qi < q; ++qi) {  
        pq[qi] = pr + qi*r;
        for (int ri = 0; ri < r; ++ri) {  
            pq[qi][ri] = ps + (qi*r + ri)*s;
        }
    }
    *array = pq;
    return 0;
}

我得承认,这很丑陋。但是所有指针和值都作为单个malloc粘在一起,允许简单的释放和单个检查以验证是否有足够的内存。

就性能而言,我不知道,但释放非常容易(只需要一个free):

int main(void) 
{
    float ***x;
    int res = malloc3dfloat(&x, 3, 4, 2);
    for (int q = 0; q < 3; ++q) {
        for (int r = 0; r < 4; ++r) {
            for (int s = 0; s < 2; ++s) {
                x[q][r][s] = rand() % 10;
            }
        }
    }

    for (int q = 0; q < 3; ++q) {
        for (int r = 0; r < 4; ++r) {
            for (int s = 0; s < 2; ++s) {
                printf("%f ", x[q][r][s]);
            }
            printf("\n");
        }
        printf("---\n");
    }
    
    free(x);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接