分配连续内存

3

我正在尝试在C语言中分配一个大的连续内存空间,并将其输出给用户。我的策略是创建两个指针(一个指向双精度浮点数,一个指向指向双精度浮点数的指针),将其中一个指针(指向指向双精度浮点数的指针)malloc到整个大小(m * n),然后将第二个指针malloc到大小为m。最后一步是遍历m的大小并执行指针算术运算,以确保大数组中的双精度浮点数的地址将存储在连续的内存中。这是我的代码。但当我打印出地址时,它似乎不是连续的(或任何顺序)。如何正确地打印出双精度浮点数的内存地址(它们全部都是值为0.0)?

/* correct solution, with correct formatting */


/*The total number of bytes allocated was:  4
0x7fd5e1c038c0 - 1
 0x7fd5e1c038c8 - 2
 0x7fd5e1c038d0 - 3
 0x7fd5e1c038d8 - 4*/

 double **dmatrix(size_t m, size_t n);

int main(int argc, char const *argv[])
{
    int m,n,i;
    double ** f;
    m = n = 2;  
    i = 0;

    f = dmatrix(sizeof(m), sizeof(n));

    printf("%s %d\n", "The total number of bytes allocated was: ", m * n);
    for (i=0;i<n*m;++i) {
        printf("%p - %d\n ", &f[i], i + 1);
    }
    return 0;
}

double **dmatrix(size_t m, size_t n) {

    double ** ptr1 = (double **)malloc(sizeof(double *) * m * n);
    double * ptr2 = (double *)malloc(sizeof(double) * m);

    int i;
    for (i = 0; i < n; i++){
        ptr1[i] = ptr2+m*i;
    }


    return ptr1;
}
2个回答

14

记住,内存只是内存。听起来平凡,但许多人似乎认为在C语言中分配内存和管理内存是某种魔法。事实并非如此。在一天结束时,你所需的内存就是你分配的,完成后就释放它。

所以从最基本的问题开始:如果需要 'n' 个 double 值,你会如何分配它们?

double *d1d = calloc(n, sizeof(double));
// ... use d1d like an array (d1d[0] = 100.00, etc. ...
free(d1d);

很简单。下一个问题分两部分,其中第一部分与内存分配无关

  1. 大小为m*n的2D数组中有多少个double值?
  2. 我们如何分配足够的内存来容纳它们。

答案:

  1. 在大小为m*n的m*n 2D双精度矩阵中,有m*n个双精度浮点数。
  2. 分配足够的内存以容纳(m*n)个双精度浮点数。

看起来很简单:

size_t m=10;
size_t n=20;
double *d2d = calloc(m*n, sizeof(double));

但是我们如何访问实际元素呢?需要进行一些数学计算。知道mn后,你可以简单地执行以下操作:

size_t i = 3; // value you want in the major index (0..(m-1)).
size_t j = 4; // value you want in the minor index (0..(n-1)).
d2d[i*n+j] = 100.0;

有没有更简单的方法来做这件事?在标准C中,;在C++中,没有。标准C支持一个非常方便的功能,可以生成正确的代码来声明动态大小的可索引数组:

size_t m=10;
size_t n=20;
double (*d2d)[n] = calloc(m, sizeof(*d2d));

不能再强调一下:标准C支持这种方法,但是C++不支持。如果您正在使用C++,您可能需要编写一个对象类来为您完成所有操作,因此除此之外将不再提到此方法。
那么上面的代码究竟是做什么的呢?首先,显而易见的是我们仍然分配了与之前相同数量的内存,即m*n个元素,每个元素的大小为sizeof(double)。但是你可能会问自己,“变量声明是什么意思?”这需要一些解释。
这里有一个明显的区别:
double *ptrs[n];  // declares an array of `n` pointers to doubles.

并且这个:

double (*ptr)[n]; // declares a pointer to an array of `n` doubles.

编译器现在知道每行有多少个双精度数字(n个),因此我们现在可以使用两个索引来引用数组中的元素:
size_t m=10;
size_t n=20;
double (*d2d)[n] = calloc(m, sizeof(*d2d));
d2d[2][5] = 100.0; // does the 2*n+5 math for you.
free(d2d);

我们能将其扩展到3D吗?当然可以,数学会变得有些奇怪,但仍然只是向大块RAM中进行偏移计算。首先是“自己做数学”的方法,使用[i,j,k]索引:

size_t l=10;
size_t m=20;
size_t n=30;
double *d3d = calloc(l*m*n, sizeof(double));

size_t i=3;
size_t j=4;
size_t k=5;
d3d[i*m*n + j*m + k] = 100.0;
free(d3d);

你需要花一分钟仔细观察这个公式,才能真正理解它是如何计算那个大块 RAM 中的 double 值所在位置的。使用上述尺寸和所需索引,"raw" 索引为:
i*m*n = 3*20*30 = 1800
j*m   = 4*20    =   80
k     = 5       =    5
======================
i*m*n+j*m+k     = 1885

所以我们正在访问那个大线性块中的第1885个元素。让我们再做一次。[0,1,2]怎么样?

i*m*n = 0*20*30 =    0
j*m   = 1*20    =   20
k     = 2       =    2
======================
i*m*n+j*m+k     =   22

即线性数组中的第22个元素。

现在应该显而易见,只要您保持在数组的自定义范围内,i:[0..(l-1)], j:[0..(m-1)], and k:[0..(n-1)] 任何有效的索引三元组都将定位到线性数组中唯一的一个值,而其他有效的三元组也不会定位到这个值。

最后,我们使用与2D数组相同的数组指针声明,但将其扩展为3D:

size_t l=10;
size_t m=20;
size_t n=30;
double (*d3d)[m][n] = calloc(l, sizeof(*d3d));
d3d[3][4][5] = 100.0;
free(d3d);

再次强调,这实际上只是我们手工计算时所做的相同数学运算,但现在是让编译器为我们完成。

我意识到这可能有点难以理解,但这很重要。如果你需要连续存储的内存矩阵(比如向OpenGL等图形渲染库提供矩阵),你可以使用以上技术相对轻松地实现。

最后,你可能会想为什么有人会一开始就使用指针数组、指向指针数组的指针、指向指针数组的指针数组等等这些东西,而不直接这样做呢?有很多原因。例如,如果你要替换行,交换指针很容易;但复制整行非常耗费资源。还有一个不太明显的答案:如果行之间的宽度需要彼此独立(例如,第0行可以是5个元素,第1行可以是6个元素),那么固定大小为l * m * n的分配方法就行不通了。

祝你好运。


1

没关系,我已经想通了。

  /* The total number of bytes allocated was:  8
    0x7fb35ac038c0 - 1
     0x7fb35ac038c8 - 2
     0x7fb35ac038d0 - 3
     0x7fb35ac038d8 - 4
     0x7fb35ac038e0 - 5
     0x7fb35ac038e8 - 6
     0x7fb35ac038f0 - 7
     0x7fb35ac038f8 - 8 */

double ***d3darr(size_t l, size_t m, size_t n);

int main(int argc, char const *argv[])
{
    int m,n,l,i;
    double *** f;
    m = n = l = 10; i = 0;

    f = d3darr(sizeof(l), sizeof(m), sizeof(n));
    printf("%s %d\n", "The total number of bytes allocated was: ", m * n * l);
    for (i=0;i<n*m*l;++i) {
        printf("%p - %d\n ", &f[i], i + 1);
    }

    return 0;
}

double ***d3darr(size_t l, size_t m, size_t n){

    double *** ptr1 = (double ***)malloc(sizeof(double **) * m * n * l);
    double ** ptr2 = (double **)malloc(sizeof(double *) * m * n);
    double * ptr3 = (double *)malloc(sizeof(double) * m);

    int i, j;
    for (i = 0; i < l; ++i) {
        ptr1[i] = ptr2+m*n*i;
        for (j = 0; j < l; ++j){
            ptr2[i] = ptr3+j*n;
        }
    }

    return ptr1;
}

所以你不想将结束值存储在连续的内存中,只想要指向它们的指针?好的。 - WhozCraig
你的意思是什么?我错过了什么吗? - The Internet
我认为你没有漏掉任何东西。我可能误解了问题。当你说你想要在连续的内存中分配矩阵时,我理解为整个矩阵需要一个内存块(这是可行的,而不是在你自己的答案中使用所有那些mallocs)。如果你有兴趣,我可以在另一个答案中发布一个演示此功能的代码示例,尽管你可能已经知道如何做了。 - WhozCraig
那太好了。我想我要做的是将内存并排分配。因此,整个矩阵之间没有空格。 - The Internet
1
好的。我会尝试为您准备一些示例代码,演示如何在C语言中使用单个分配来进行2D和3D工作。 - WhozCraig

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