困惑于指向指针和动态内存分配

7

我很难完全理解正在发生的事情。

以下是我对底部代码的理解:

  1. 将A定义为指向指针地址的指针,该指针指向double类型的地址。

  2. 在堆上分配4个内存块,每个内存块都可以容纳一个指向double类型的地址。返回第一个分配给A的块的地址。

  3. 在堆上分配6个内存块,每个内存块都可以容纳double数据类型,并返回前面4个块中每个块的第一个地址。

  4. 假设A[0]更改了A所指向的地址上的值而不是A本身,则变量A仍包含四个指针中第一个块的地址。

现在这就是链接在底部的图表发挥作用的地方。假设我想访问存储在红色块中的数据。我该如何做呢?

以下代码是否有效?

double data = *(*(A + 3) + 2);

我认为,A是一个指针,并且将其增加3应该会将地址增加与大小(double *)相同的字节数。对地址(A + 3)进行取消引用,我将得到第一个块的地址(假设它没有被重新分配)。通过增加所得到的地址2次并取消引用该地址,我应该得到红色块的值。

所以,如果我理解正确,在内存中存储了1 + M个指针。为什么?如果目标是存储MxN数据,为什么需要1 + M个指针?这些额外的指针似乎只有在想定义不同大小的行时才有帮助,但目标是定义矩形数组数据。

const int M = 4;
const int N = 6;
double **A;

A = malloc(M*sizeof(double *));

for (i = 0; i < N; i++) {
    A[i]= malloc(N*sizeof(double));
}

图示:

在这里输入图片描述

附注: 我是一名电气工程学生,对C语言和正式的编程实践不太熟悉。我知道我对这么简单的代码比较苛刻,但我想确定我正确理解了指针和内存分配。它们一眼看上去就很难理解。这是我教授给我的一个矩阵乘法代码的一部分,我想把指针传递给一个函数,并让它访问和修改内存中的正确值。个人认为对于这段代码,我更喜欢看到二维数组,但我假设有一个很好的理由。

3个回答

3
你也可以将此内容连续存储,而不是让编译器为你执行数学运算。因此:
double * A = malloc (M * N * sizeof(double));

double get(double * what, cur_x, cur_y, max_x, max_y)
{
    return *(what+cur_x*max_y+cur_y);
}

由于您知道大小,因此这将节省所有间接操作。

如果您不需要动态分配大小,则使用double A[17][14] 可以在栈上为您完成所需操作


2

malloc(M*sizeof(double *))的意思是:“给我一个指向M个双精度浮点数指针的指针。” malloc(N*sizeof(double))的意思是:“给我一个指向N个双精度浮点数的指针。” 现在请注意bug:

for (i = 0; i < N; i++) {
    A[i]= ...;
}

A 指向一个包含 M 个双指针的数组,但你正在向其中写入 N 个条目(每个条目都是指向 N 个双精度浮点数的指针)。如果 M > N,则会浪费空间,而如果 M < N,则会使用未经许可写入的空间。


2

是的,double data = *(*(A + 3) + 2);可以运行,并访问“红色”的双精度浮点数。

我猜你的教授很快就会告诉你,A[3][2]也可以工作,更易读,并且比*(*(A + 3) + 2);更可取。

得到的教训(尚未学到,但你的教授正试图教给你的):在C中,多维数组并不像“具有N列和M行的表格”,在C中,所有数组实际上都是指针,并且多维数组被实现为指向指针的指针。每当你写A[3][2]时,内部实际发生的是*(*(A + 3) + 2)。这反过来解释了为什么不能在一条语句中分配一个N*M矩阵;必须先分配“行指针”,然后为每个行指针分配“列”。


在C语言中,所有的数组都是指针这种说法是不正确的。虽然数组会退化成指针,并且可以使用指针进行数组下标操作,但它们并不相同。更多信息请参考:https://dev59.com/bFzUa4cB1Zd3GeqPzxyj。“你不能在一条语句中分配一个NM矩阵”这种说法也是不正确的。实际上,你可以使用double A[M][N];来分配一个NM的矩阵,尽管对于大型矩阵,你不想将其分配在堆栈上。你可以使用VLA(可变长度数组)来分配内存,例如:double (*A)[N] = malloc(M * sizeof *A); - Kninnug
我想我刚才是重复了,在主贴中我所谈论的是链表,对吧?所有数组都是链表吗? - Altermeris
不,数组是一块可以连续寻址的内存块:int arr[10] = {...}; 然后你可以访问 arr[0]arr[1] 直到 arr[9]。这些语句等同于:*(&arr[0] + 0)*(&arr[0] + 1) 等(&arr[0] 是第一个元素的地址,如果我写成 *(arr + 1) 它会超出数组的末尾,你也可以这样做:int * ptr = arr; 然后使用 *(ptr + 0)*(ptr + 1),因为数组会退化为指针)。双指针看起来像链表,但与真正的链表不同,除了它所指向的地址之外,指针本身没有(其他)数据。 - Kninnug
更正我的上一条评论:*(arr + 1) 等同于 *(&arr[0] + 1),因此不必取第一个元素的地址。我不知道为什么会有这种想法。 - Kninnug

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