创建指向二维数组的指针

我需要一个指向静态二维数组的指针。这是如何做到的?

static uint8_t l_matrix[10][20];


void test(){
uint8_t **matrix_ptr = l_matrix; //wrong idea
}

我会遇到各种各样的错误,比如:

  • 警告: 来自不兼容指针类型的赋值
  • 下标值既不是数组也不是指针
  • 错误: 无效使用灵活的数组成员
257645 次浏览

你可以这样做:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

通过将数组声明为线性并自己对数组索引进行(row,col)计算,总是可以避免与编译器纠缠不清。

static uint8_t l_matrix[200];


void test(int row, int col, uint8_t val)


{


uint8_t* matrix_ptr = l_matrix;
matrix_ptr [col+y*row] = val; // to assign a value


}

这是编译器无论如何都会做的。

这里需要一个指向数组第一个元素的指针

uint8_t (*matrix_ptr)[20] = l_matrix;

使用 typedef,这看起来更干净

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

然后你就可以重新享受生活了:)

matrix_ptr[0][1] = ...;

注意 C 语言中的 指针/数组世界,这里有很多困惑。


剪辑

在这里查看其他一些答案,因为注释字段太短,不能在这里查看。人们提出了多种替代方案,但没有展示它们的行为。他们是这么做的

uint8_t (*matrix_ptr)[][20] = l_matrix;

如果您修复了错误并添加了操作符 &的地址,如下面的代码片段所示

uint8_t (*matrix_ptr)[][20] = &l_matrix;

然后创建一个指针,指向一个不完整的数组类型,类型为20 uint8 _ t 的数组元素。因为指针指向一个数组数组,所以必须使用

(*matrix_ptr)[0][1] = ...;

因为它是一个指向不完整数组的指针,所以 不能是一个快捷方式

matrix_ptr[0][0][1] = ...;

因为建立索引需要知道元素类型的大小(建立索引意味着向指针添加一个整数,所以它不能用于不完整的类型)。注意,这只适用于 C,因为 T[]T[N]是兼容类型。C + + 没有 兼容类型的概念,因此它将拒绝该代码,因为 T[]T[10]是不同的类型。


下面的替代方法根本不起作用,因为当您将数组视为一维数组时,数组的元素类型是 没有 uint8_t,而不是 uint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

以下是一个很好的选择

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

你可以用

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

它的好处是可以保持外维度的大小,所以你可以在它上面应用 sizeof

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

还有一个答案可以利用数组中的项是连续存储的这一事实

uint8_t *matrix_ptr = l_matrix[0];

现在,形式上只允许您访问二维数组的第一个元素的元素。也就是说,以下条件成立

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid


matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

您可能会注意到,它可以工作到 10*20-1,但是如果进行别名分析和其他积极的优化,一些编译器可能会做出可能破坏该代码的假设。话虽如此,我从来没有遇到过一个编译器在它上面失败(但是话又说回来,我没有在实际代码中使用过这种技术) ,甚至 C FAQ 也包含了这种技术(有一个关于它的 UB‘ ness 的警告) ,如果你不能改变数组类型,这是最后一个可以拯救你的选项:)

你好,

宣言

static uint8_t l_matrix[10][20];

已经为20个 unit8 _ t 位置的10行预留了存储空间,即200个 uint8 _ t 大小的位置, 通过计算20 x row + column 找到每个元素。

所以没有

uint8_t (*matrix_ptr)[20] = l_matrix;

给你你需要的并且指向数组第一行的列0元素?

编辑: 进一步考虑这个问题,根据定义,数组名不是一个指针吗?也就是说,数组的名称是第一个元素位置的同义词,即 l _ Matrix [0][0] ?

编辑2: 正如其他人提到的,评论空间有点小,无法进一步讨论。无论如何:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

不为有问题的数组提供任何存储分配。

如上所述,并按照标准的定义,声明:

static uint8_t l_matrix[10][20];

已经留出了 uint8 _ t 类型的200个顺序位置。

使用形式语句引用 l _ Matrix:

(*l_matrix + (20 * rowno) + colno)

将给出在 row rowno 中找到的 colno‘ th 元素的内容。

所有的指针操作都会自动考虑指向的对象的大小。-K & R Section 5.4,p. 103

如果在手头对象的存储中涉及到任何填充或字节对齐移位,也会出现这种情况。编译器将自动调整这些。根据 C ANSI 标准的定义。

高温

干杯,

您需要一个指向第一个元素的指针,因此;

static uint8_t l_matrix[10][20];


void test(){
uint8_t *matrix_ptr = l_matrix[0]; //wrong idea
}

进去

int *ptr= l_matrix[0];

你可以访问

*p
*(p+1)
*(p+2)

在所有二维数组也存储为1-d 之后。

在 C99(由 clang 和 gcc 支持)中,有一种模糊的语法用于通过引用将多维数组传递给函数:

int l_matrix[10][20];


void test(int matrix_ptr[static 10][20]) {
}


int main(void) {
test(l_matrix);
}

与普通指针不同,这暗示了数组的大小,理论上是的允许编译器对传递过小的数组发出警告,并指出明显的出界访问。

遗憾的是,它没有修复 sizeof(),而且编译器似乎还没有使用这些信息,所以它仍然是一个有趣的问题。

如果要使用负索引,还可以添加偏移量:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

如果编译器提供错误或警告,可以使用:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;

为了让 完全理解这一点,你需要掌握以下概念:

数组不是指针!

首先(已经说得够多了) ,数组不是指针。相反,在大多数情况下,它们会“衰减”到它们的第一个元素的地址,这个地址可以被赋予一个指针:

int a[] = {1, 2, 3};


int *p = a; // p now points to a[0]

我假设它是这样工作的,这样就可以访问数组的内容而无需复制所有内容。这只是数组类型的一种行为,并不意味着它们是相同的。



多维数组

多维数组只是以编译器/机器可以理解和操作的方式对内存进行“分区”的一种方式。

例如,int a[4][3][5] = 一个包含4 * 3 * 5(60)个整数大小内存块的数组。

与使用 int a[4][3][5]相比,使用普通 int b[60]的优势在于它们现在是“分区的”(如果需要,更容易处理它们的“块”) ,并且程序现在可以执行绑定检查。

事实上,int a[4][3][5]int b[60]一样存储在内存中—— 只有的不同之处在于,程序现在管理它的方式就好像它们是具有特定大小的独立实体(具体来说,是由三组五个组成的四组)。

请记住: int a[4][3][5]int b[60]在内存中是相同的,唯一的区别是应用程序/编译器如何处理它们

{
{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, 30}
}
{
{31, 32, 33, 34, 35}
{36, 37, 38, 39, 40}
{41, 42, 43, 44, 45}
}
{
{46, 47, 48, 49, 50}
{51, 52, 53, 54, 55}
{56, 57, 58, 59, 60}
}

从这里,您可以清楚地看到,每个“分区”只是程序跟踪的一个数组。



语法

现在,数组在语法上与指针不同。具体来说,这意味着 编译器/机器将对它们区别对待。这可能看起来像一个不需要思考的问题,但是看看这个:

int a[3][3];


printf("%p %p", a, a[0]);

上面的示例打印相同的内存地址两次,如下所示:

0x7eb5a3b4 0x7eb5a3b4

然而,只有一个指针可以如此直接地指向 :

int *p1 = a[0]; // RIGHT !


int *p2 = a; // WRONG !

为什么不能 a 被赋予一个指针,但 a[0] 可以?

这是多维数组的结果,我将解释其原因:

在“ a”的层次上,我们仍然看到我们还有另一个“维度”值得期待。然而,在“ a[0]”级别上,我们已经在顶维上了,所以就程序而言,我们只是在看一个普通的数组。

你可能会问:

如果数组是多维的,为什么要为它制作一个指针呢?

最好这样想:

多维数组的“衰减”不仅仅是一个地址,还包括 带有分区数据的地址(也就是它仍然明白其底层数据是由其他数组组成的) ,它由数组在第一维之外设置的边界组成。

这种“分区”逻辑不能存在于指针中,除非我们指定它:

int a[4][5][95][8];


int (*p)[5][95][8];


p = a; // p = *a[0] // p = a+0

否则,将丢失数组排序属性的含义。

还要注意在 *p: int (*p)[5][95][8]周围使用圆括号-这是为了指定我们创建一个具有这些边界的指针,而不是一个具有这些边界的指针数组: int *p[5][95][8]



结论

让我们回顾一下:

  • 如果数组在所使用的上下文中没有其他用途,它们就会衰减为地址
  • 多维数组只是数组的数组-因此,“衰减”地址将承担“我有子维”的负担
  • 指针 除非你给它中不能存在维度数据。

简而言之: 多维数组衰减为具有理解其内容能力的地址。

初始化指向多维数组的指针的基本语法是

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

调用它的基本语法是

(*pointer_name)[1st index][2nd index][...]

下面是一个例子:

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


int main() {
// The multidimentional array...
char balance[5][100] = {
"Subham",
"Messi"
};


char (*p)[5][100] = &balance; // Pointer initialization...


printf("%s\n",(*p)[0]); // Calling...
printf("%s\n",(*p)[1]); // Calling...


return 0;
}

产出为:

Subham
Messi

成功了。