【问题标题】:Segmentation Fault error in 3D array memory allocation3D 阵列内存分配中的分段错误错误
【发布时间】:2013-09-02 19:21:07
【问题描述】:

我在 C 中有一个指针变量 int ***a。我将它作为 &a 即引用传递给函数。在函数中,我得到了一个int ****a 类型的指针变量。 我是这样分配内存的。

*a=(int***)malloc(no1*sizeof(int**));
some loop from 0 to no1
    (*a)[++l]=(int**)malloc((no1+1)*sizeof(int*));
some loop from 0 to no1
    (*a)[l][h]=(int*)malloc(2*sizeof(int));

这只是我分配内存的时间。没有给出实际程序;这里没有错误。 但是当我要这样做时:

(*a)[l][h][0]=no1;

它给了我一个“分段错误”错误,我不明白为什么。

更新: 我写了一个示例程序,它只分配内存。这也给出了“分段错误”错误。

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

void allocate(int ****a)
{
    int i,j,k;
    if(((*a)=(int***)malloc(5*sizeof(int**)))==NULL)
    {
        printf("\nError in allocation of double pointer array\n");
        exit(0);
    }
    for(i=0;i<5;i++)if(((*a)[i]=(int**)malloc(4*sizeof(int*)))==NULL)
    {
        printf("\nError in allocation of single pointer array on index [%d]\n",i);
        exit(0);
    }
    for(i=0;i<5;i++)
        for(j=0;j<4;i++)
            if(((*a)[i][j]=(int*)malloc(3*sizeof(int)))==NULL)
            {
                printf("\nError in allocation of array on index [%d][%d]\n",i,j);
                exit(0);
            }
    for(i=0;i<5;i++)
        for(j=0;j<4;i++)
            for(k=0;k<3;k++)
                (*a)[i][j][k]=k;
}

main()
{
    int ***a;
    int i,j,k;
    allocate(&a);
    for(i=0;i<5;i++)
        for(j=0;j<4;i++)
            for(k=0;k<3;k++)
                printf("\na[%d][%d][%d]  = %d ",i,j,k,a[i][j][k]);
}

【问题讨论】:

  • 这不是 3D 数组。那是一个指针对指针的指针。还有being a three-star programmer is not a compliment.
  • 如果您可以扩展分配循环的伪代码,这可能会有所帮助。你也可以在段错误的代码中说明 l 和 h 是什么。
  • @JayadrathaMondal 如果您使用 actual 3D 数组,而不是添加 another 级别的间接性,该怎么办? (或者至少是指向其第一个元素的指针......)像这样:int (*arr)[y][z] = malloc(x * sizeof(arr[0]));
  • @H2CO3 假设 VLA 已经过测试并声称支持您的实现(大多数都这样做),我同意,这是一种更清洁的方法。 (我会用 VLA 检查sizeof(),但是,我很确定它不起作用,你仍然需要手动计算)。
  • @JayadrathaMondal:请发布实际的 C 代码。您可能在分配代码中有错误。否则我们无法为您提供帮助(除了风格建议;))

标签: c


【解决方案1】:

问题中的修改代码

您的代码有:

for(i=0;i<5;i++)
    for(j=0;j<4;i++)

好几次。第二个循环应该递增 j,而不是 i。复制粘贴时要非常小心。

此代码不会崩溃(但会泄漏)。

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

void allocate(int ****a);

void allocate(int ****a)
{
    int i,j,k;
    printf("allocate: 1B\n");
    if(((*a)=(int***)malloc(5*sizeof(int**)))==NULL)
    {
        printf("\nError in allocation of double pointer array\n");
        exit(0);
    }
    printf("allocate: 1A\n");

    printf("allocate: 2B\n");
    for(i=0;i<5;i++)
        if(((*a)[i]=(int**)malloc(4*sizeof(int*)))==NULL)
        {
            printf("\nError in allocation of single pointer array on index [%d]\n",i);
            exit(0);
        }
    printf("allocate: 2A\n");
    printf("allocate: 3B\n");
    for(i=0;i<5;i++)
        for(j=0;j<4;j++)
            if(((*a)[i][j]=(int*)malloc(3*sizeof(int)))==NULL)
            {
                printf("\nError in allocation of array on index [%d][%d]\n",i,j);
                exit(0);
            }
    printf("allocate: 3A\n");

    printf("allocate: 4B\n");
    for(i=0;i<5;i++)
        for(j=0;j<4;j++)
            for(k=0;k<3;k++)
                (*a)[i][j][k]=k;
    printf("allocate: 4A\n");
}

int main(void)
{
    int ***a;
    int i,j,k;
    allocate(&a);
    for(i=0;i<5;i++)
        for(j=0;j<4;j++)
            for(k=0;k<3;k++)
                printf("a[%d][%d][%d]  = %d\n",i,j,k,a[i][j][k]);
}

以前的答案

由于您没有向我们展示大部分代码,因此很难预测您是如何处理不当的,但同样,由于您正在获得核心转储,因此您一定是处理不当。

这里有一些工作代码——没有用 valgrind 检查,因为它不适用于 Mac OS X 10.8——似乎可以工作。分配失败的错误恢复不完整,也缺少销毁全分配数组的功能。

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

static int ***allocate_3d_array(int no1, int ****a)
{
    *a = (int***)malloc(no1 * sizeof(int**));
    if (*a == 0)
        return 0;

    for (int l = 0; l < no1; l++)
    {
        if (((*a)[l]=(int**)malloc((no1+1)*sizeof(int*))) == 0)
        {
            while (l > 0)
                free((*a)[--l]);
            return 0;
        }
    }

    for (int l = 0; l < no1; l++)
    {
        for (int h = 0; h < no1; h++)
        {
            if (((*a)[l][h]=(int*)malloc(2*sizeof(int))) == 0)
            {
                /* Leak! */
                return 0;
            }
        }
    }

    for (int l = 0; l < no1; l++)
        for (int h = 0; h < no1; h++)
            for (int k = 0; k < 2; k++)
                (*a)[l][h][k] = 10000 * l + 100 * h + k;

    return *a;
}

int main(void)
{
    int no1 = 5;
    int ***a = 0;
    int ***b = allocate_3d_array(no1, &a);
    const char *pad[] = { "  ", "\n" };
    assert(b == a);

    if (a != 0)
    {
        for (int l = 0; l < no1; l++)
            for (int h = 0; h < no1; h++)
                for (int k = 0; k < 2; k++)
                    printf("a[%d][%d][%d] = %.6d%s", l, h, k, a[l][h][k], pad[k]);

        // free memory - added by harpun; reformatted by Jonathan Leffler
        // Would be a function normally — see version 2 code.
        for (int l = 0; l < no1; l++)
        {
            for (int h = 0; h < no1; h++)
                free(a[l][h]);
            free(a[l]);
        }
        free(a);
    }

    return 0;
}

样本输出:

a[0][0][0] = 000000  a[0][0][1] = 000001
a[0][1][0] = 000100  a[0][1][1] = 000101
a[0][2][0] = 000200  a[0][2][1] = 000201
a[0][3][0] = 000300  a[0][3][1] = 000301
a[0][4][0] = 000400  a[0][4][1] = 000401
a[1][0][0] = 010000  a[1][0][1] = 010001
a[1][1][0] = 010100  a[1][1][1] = 010101
a[1][2][0] = 010200  a[1][2][1] = 010201
a[1][3][0] = 010300  a[1][3][1] = 010301
a[1][4][0] = 010400  a[1][4][1] = 010401
a[2][0][0] = 020000  a[2][0][1] = 020001
a[2][1][0] = 020100  a[2][1][1] = 020101
a[2][2][0] = 020200  a[2][2][1] = 020201
a[2][3][0] = 020300  a[2][3][1] = 020301
a[2][4][0] = 020400  a[2][4][1] = 020401
a[3][0][0] = 030000  a[3][0][1] = 030001
a[3][1][0] = 030100  a[3][1][1] = 030101
a[3][2][0] = 030200  a[3][2][1] = 030201
a[3][3][0] = 030300  a[3][3][1] = 030301
a[3][4][0] = 030400  a[3][4][1] = 030401
a[4][0][0] = 040000  a[4][0][1] = 040001
a[4][1][0] = 040100  a[4][1][1] = 040101
a[4][2][0] = 040200  a[4][2][1] = 040201
a[4][3][0] = 040300  a[4][3][1] = 040301
a[4][4][0] = 040400  a[4][4][1] = 040401

将此与您所拥有的进行比较。您可以添加更多诊断打印消息。如果这没有足够的帮助,请创建一个与此类似的 SSCCE (Short, Self-Contained, Correct Example),在没有任何无关材料的情况下在您的代码中演示问题。

第二版代码

这是一个更复杂的代码版本,用于模拟 N 次分配后的内存分配失败(以及一个测试工具,它使用从 0 到 35 的每个 N 值运行它,其中实际上只有 30 次分配给数组. 它还包括释放数组的代码(类似于,但不同于harpun 编辑到我的答案中的代码。末尾与包含 PID 的行的交互意味着我可以使用 @ 检查内存使用情况987654331@ 在另一个终端窗口中。(否则,我不喜欢做那种事情的程序——我想我应该通过system() 从我的程序中运行ps,但我感觉很懒。)

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

static int fail_after = 0;
static int num_allocs = 0;

static void *xmalloc(size_t size)
{
    if (fail_after > 0 && num_allocs++ >= fail_after)
    {
        fputs("Out of memory\n", stdout);
        return 0;
    }
    return malloc(size);
}

static int ***allocate_3d_array(int no1, int ****a)
{
    *a = (int***)xmalloc(no1 * sizeof(int**));
    if (*a == 0)
        return 0;

    for (int l = 0; l < no1; l++)
    {
        if (((*a)[l]=(int**)xmalloc((no1+1)*sizeof(int*))) == 0)
        {
            for (int l1 = 0; l1 < l; l1++)
                free((*a)[l1]);
            free(*a);
            *a = 0;
            return 0;
        }
    }

    for (int l = 0; l < no1; l++)
    {
        for (int h = 0; h < no1; h++)
        {
            if (((*a)[l][h]=(int*)xmalloc(2*sizeof(int))) == 0)
            {
                /* Release prior items in current row */
                for (int h1 = 0; h1 < h; h1++)
                    free((*a)[l][h1]);
                free((*a)[l]);
                /* Release items in prior rows */
                for (int l1 = 0; l1 < l; l1++)
                {
                    for (int h1 = 0; h1 < no1; h1++)
                        free((*a)[l1][h1]);
                    free((*a)[l1]);
                }
                free(*a);
                *a = 0;
                return 0;
            }
        }
    }

    for (int l = 0; l < no1; l++)
        for (int h = 0; h < no1; h++)
            for (int k = 0; k < 2; k++)
                (*a)[l][h][k] = 10000 * l + 100 * h + k;

    return *a;
}

static void destroy_3d_array(int no1, int ***a)
{
    if (a != 0)
    {
        for (int l = 0; l < no1; l++)
        {
            for (int h = 0; h < no1; h++)
                free(a[l][h]);
            free(a[l]);
        }
        free(a);
    }
}

static void test_allocation(int no1)
{
    int ***a = 0;
    int ***b = allocate_3d_array(no1, &a);
    const char *pad[] = { "  ", "\n" };
    assert(b == a);

    if (a != 0)
    {
        for (int l = 0; l < no1; l++)
        {
            for (int h = 0; h < no1; h++)
            {
                for (int k = 0; k < 2; k++)
                {
                    if (a[l][h][k] != l * 10000 + h * 100 + k)
                        printf("a[%d][%d][%d] = %.6d%s", l, h, k, a[l][h][k], pad[k]);
                }
            }
        }
    }

    destroy_3d_array(no1, a);
}

int main(void)
{
    int no1 = 5;

    for (fail_after = 0; fail_after < 33; fail_after++)
    {
        printf("Fail after: %d\n", fail_after);
        num_allocs = 0;
        test_allocation(no1);
    }

    printf("PID %d - waiting for some data to exit:", (int)getpid());
    fflush(0);
    getchar();

    return 0;
}

注意记忆恢复是多么痛苦。和以前一样,没有用valgrind 测试,但我从harpun 对以前版本的测试中得到保证。

第 3 版 — 来自 valgrind 的清理健康单

此代码与版本 2 中的测试非常相似。它修复了叶级分配中内存分配失败时清理中的内存泄漏。程序不再提示输入(更可取);它需要一个可选的单个参数,即失败后的分配次数。使用valgrind 进行的测试表明,参数 0-6 没有泄漏,但参数 7 存在泄漏。很快就发现了问题并加以解决。 (运行valgrind 的机器可用时会更容易——它在长周末断电以进行一般站点电源升级。)

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

static int fail_after = 0;
static int num_allocs = 0;

static void *xmalloc(size_t size)
{
    if (fail_after > 0 && num_allocs++ >= fail_after)
    {
        fputs("Out of memory\n", stdout);
        return 0;
    }
    return malloc(size);
}

static int ***allocate_3d_array(int no1, int ****a)
{
    *a = (int***)xmalloc(no1 * sizeof(int**));
    if (*a == 0)
        return 0;

    for (int l = 0; l < no1; l++)
    {
        if (((*a)[l]=(int**)xmalloc((no1+1)*sizeof(int*))) == 0)
        {
            for (int l1 = 0; l1 < l; l1++)
                free((*a)[l1]);
            free(*a);
            *a = 0;
            return 0;
        }
    }

    for (int l = 0; l < no1; l++)
    {
        for (int h = 0; h < no1; h++)
        {
            if (((*a)[l][h]=(int*)xmalloc(2*sizeof(int))) == 0)
            {
                /* Release prior items in current (partial) row */
                for (int h1 = 0; h1 < h; h1++)
                    free((*a)[l][h1]);
                /* Release items in prior (complete) rows */
                for (int l1 = 0; l1 < l; l1++)
                {
                    for (int h1 = 0; h1 < no1; h1++)
                        free((*a)[l1][h1]);
                }
                /* Release entries in first (complete) level of array */
                for (int l1 = 0; l1 < no1; l1++)
                    free((*a)[l1]);
                free(*a);
                *a = 0;
                return 0;
            }
        }
    }

    for (int l = 0; l < no1; l++)
        for (int h = 0; h < no1; h++)
            for (int k = 0; k < 2; k++)
                (*a)[l][h][k] = 10000 * l + 100 * h + k;

    return *a;
}

static void destroy_3d_array(int no1, int ***a)
{
    if (a != 0)
    {
        for (int l = 0; l < no1; l++)
        {
            for (int h = 0; h < no1; h++)
                free(a[l][h]);
            free(a[l]);
        }
        free(a);
    }
}

static void test_allocation(int no1)
{
    int ***a = 0;
    int ***b = allocate_3d_array(no1, &a);
    const char *pad[] = { "  ", "\n" };
    assert(b == a);

    if (a != 0)
    {
        for (int l = 0; l < no1; l++)
        {
            for (int h = 0; h < no1; h++)
            {
                for (int k = 0; k < 2; k++)
                {
                    if (a[l][h][k] != l * 10000 + h * 100 + k)
                        printf("a[%d][%d][%d] = %.6d%s", l, h, k, a[l][h][k], pad[k]);
                }
            }
        }
    }

    destroy_3d_array(no1, a);
}

int main(int argc, char **argv)
{
    int no1 = 5;
    int fail_limit = 33;

    if (argc == 2)
        fail_limit = atoi(argv[1]);

    for (fail_after = 0; fail_after < fail_limit; fail_after++)
    {
        printf("Fail after: %d\n", fail_after);
        num_allocs = 0;
        test_allocation(no1);
    }

    return 0;
}

版本 4 - 更少的内存分配

2014 年 12 月 20 日更新

上面的代码进行了大量的内存分配,这使得释放和错误恢复变得复杂。这是一个替代版本,它只进行 3 次分配,一个用于指向指针的指针向量,一个用于指针数组,一个用于整数数组。然后它将指针设置为指向内存中的正确位置。

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

static int fail_after = 0;
static int num_allocs = 0;

static void *xmalloc(size_t size)
{
    if (fail_after > 0 && num_allocs++ >= fail_after)
    {
        fputs("Out of memory\n", stdout);
        return 0;
    }
    return malloc(size);
}

static int ***allocate_3d_array(int no1, int ****a)
{
    int ***d0 = (int***)xmalloc(no1 * sizeof(int**));
    int **d1 = (int **)xmalloc(no1 * no1 * sizeof(int *));
    int *d2 = (int *)xmalloc(no1 * no1  * 2 * sizeof(int));

    if (d0 == 0 || d1 == 0 || d2 == 0)
    {
        free(d0);
        free(d1);
        free(d2);
        *a = 0;
        return 0;
    }

    for (int l = 0; l < no1; l++)
    {
        d0[l] = &d1[l * no1];
        for (int h = 0; h < no1; h++)
        {
            d0[l][h] = &d2[(l * no1 + h) * 2];
            for (int k = 0; k < 2; k++)
                d0[l][h][k] = l * 10000 + h * 100 + k;
        }
    }

    *a = d0;
    return *a;
}

static void destroy_3d_array(int ***a)
{
    if (a != 0)
    {
        free(a[0][0]);
        free(a[0]);
        free(a);
    }
}

static void test_allocation(int no1)
{
    int ***a = 0;
    int ***b = allocate_3d_array(no1, &a);
    const char *pad[] = { "  ", "\n" };
    assert(b == a);

    if (a != 0)
    {
        for (int l = 0; l < no1; l++)
        {
            for (int h = 0; h < no1; h++)
            {
                for (int k = 0; k < 2; k++)
                {
                    if (a[l][h][k] != l * 10000 + h * 100 + k)
                        printf("Oops: a[%d][%d][%d] = %.6d%s", l, h, k, a[l][h][k], pad[k]);
                }
            }
        }
    }

    destroy_3d_array(a);
}

int main(int argc, char **argv)
{
    int no1 = 5;
    int fail_limit = 4;

    if (argc == 2)
        fail_limit = atoi(argv[1]);

    for (fail_after = 0; fail_after < fail_limit; fail_after++)
    {
        printf("Fail after: %d\n", fail_after);
        num_allocs = 0;
        test_allocation(no1);
    }

    return 0;
}

这在 Mac OS X 10.10.1 上使用 GCC 4.9.1 的健康状况良好,使用 valgrind 版本 valgrind-3.11.0.SVN 进行了检查(从 SVN 树构建,并针对 Mac OS X 进行了一些必要的修复,但没有足够的抑制)。

在我开发答案时触发了诊断打印(以“糟糕”开头);我当时的指针计算错误。

【讨论】:

  • +1 .. 但不应该避免 malloc 的返回值的强制转换吗?
  • 分配有效。我用 valgrind 进行了检查,并添加了缺失的内存清理代码。
  • 我检查了分配时间检查是否分配。就像你所做的一样。没有错误。只有当我通过给出所有 3 个索引错误在数组中插入值时才会发生。一切都像您的代码一样完成。 :(
  • @JayadrathaMondal:请显示实际代码。也许它就像循环中的一个错误一样简单。
  • @undur_gongor:这取决于您与谁交谈。如果你问这里的大多数 C 贡献者,演员阵容是不可取的。我学会了在强制转换的系统上编程 C,在 C89 标准之前的日子里,malloc() 返回char *,并且在地址的char * 值的位模式为的机器上不同于同一地址的int * 值(因此不强制转换会导致崩溃)。我不会因为人们做了曾经必要的事情而责备他们。这是明确的。如果您始终需要使用原型进行编译,那么人们担心的问题就不会发生。
【解决方案2】:

抱歉,直言不讳:这是一种处理 3D 数组的可怕方式:一个双嵌套循环,其中包含大量对 malloc() 的调用,然后是三重间接以在运行时获取值。呸! :o)

执行此操作的常规方法(在 HPC 社区中)是使用一维数组并自己进行索引计算。假设索引ix方向上迭代nxplanesj方向上迭代nypencils >y 方向,kz 方向上迭代 nz 单元格。那么铅笔有nz元素,平面有nz*ny元素,整个“砖”有nz*ny*nx元素。因此,您可以遍历整个结构:

for(i=0; i<nx; i++) {
    for(j=0; j<ny; j++) {
        for(k=0; k<nz; k++) {
            printf("a(%d,%d,%d) = %d\n", i, j, k, a[(i*ny+j)*nz+k]);
        }
    }
}

这种构造的优点是您可以通过对malloc() 的一次调用来分配它,而不是一大堆嵌套调用:

int *a;
a = malloc(nx*ny*nz*sizeof(int));

x=a[i][j][k] 构造具有三个间接级别:您必须从内存中获取地址,a,添加偏移量,i,从内存中获取该地址,a[i],添加偏移量,@ 987654338@,从内存中获取该地址a[i][j],添加偏移量k,并(最后)获取数据a[i][j][k]。所有这些中间指针都在浪费缓存行和 TLB 条目。

x=a[(i*ny+j)*nz+k] 的构造具有 一个 级间接性,代价是两个额外的整数乘法:计算偏移量,从内存中获取地址,'a',计算并添加偏移量,( i*ny+j)*nz+k,取数据。

此外,基本上没有办法无论如何基于数据访问模式来提高三重间接方法的性能。如果我们实际上访问了每个单元格,我们可以做这样的事情来避免索引计算的一些开销。

ij = 0;
for(i=0; i<nx; i++) {
    ii=i*ny;
    for(j=0; j<ny; j++) {
        ij=(ii+j)*nz;
        for(k=0; k<nz; k++) {
            printf("a(%d,%d,%d) = %d\n", i, j, k, a[ij+k]);
        }
    }
}

根据您的操作,这可能也不是很好,并且所有替代布局和索引方法(例如 Morton 或 Ahnenteufel 索引)可能更合适,具体取决于您的访问模式。我并不是要给出关于 3D 笛卡尔网格表示或索引的完整论文,只是说明“三星”解决方案由于多种原因非常糟糕。

【讨论】:

  • +1:我同意您的结论,即 3D 数组可以更好地处理,如您所示,而不是问题中所示。另一方面,如果编码(相当多)小心,3D 符号可以工作。令人讨厌的是,OP 不会显示崩溃的真实代码,因为很难诊断不可见代码中的问题。
  • 我同意 3D 符号可以工作(作为传统 1D 解决方案之上的一层),但代价是更多的内存 ((nx+ny)*sizeof(int( *)) 并在运行时进行大量额外的取消引用。对于非常小的网格,也许可以,但我认为没有理由不以传统方式开始。如果你真的很想拥有“i,j, k” 风格的索引,你可以用宏来做。
【解决方案3】:

通过使用(*a)[l][h][0],您试图取消引用普通的 int 而不是指针。

直接使用a[l][h][0]为其赋值。

【讨论】:

  • 实际变量在 main() 中,即 ***a。它在函数中作为参数 &a 传递。所以在a前面使用*
  • 你是如何声明函数 1[ funcname (int**** a) {...}
  • 同意上面的观点,我们需要一个完整的例子,分配代码可能有问题[比如 no1 是 0 等]
猜你喜欢
  • 2019-04-18
  • 1970-01-01
  • 2016-09-29
  • 1970-01-01
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 2018-11-11
  • 2021-06-14
相关资源
最近更新 更多