创建一个指向二维数组的指针

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

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

我得到各种类似的错误:

  • 警告:从不兼容的指针类型分配
  • 下标值既不是数组也不是指针
  • 错误:使用灵活的数组成员无效

在这里你要做一个指向数组的第一个元素的指针

 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常见问题解答包含的技术(与其UB'ness警告),如果你不能改变数组类型,这是保存你的最后一个选项:)

充分了解这一点,您必须掌握以下概念:

数组不是指针!

首先(它已被足够的讲解), 数组不是指针 。 相反,在大多数情况下,它们会“衰减”到它们的第一个元素,这个元素可以被分配给一个指针:

 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] vs plain 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[0] 可以?

这只是多维数组的结果,我将解释为什么:

在' a '的层面上,我们仍然看到我们还有另一个“维度”值得期待。 然而,在' a[0] '的层面上,我们已经处于最高维度,所以就程序而言,我们只是看一个正常的数组。

你可能会问:

为什么这个数组是多维的呢?

最好这样想:

一个多维数组的“衰减”不仅仅是一个地址,而是一个带有分区数据的地址 (它仍然理解它的底层数据是由其他数组构成的),它由数组在第一维以外设置的边界组成。

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

 int a[4][5][95][8]; int (*p)[5][95][8]; p = a; // p = *a[0] // p = a+0 

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

还要注意围绕*pint (*p)[5][95][8]的圆括号的使用 – 这是要指定我们正在使用这些边界的指针,而不是具有这些边界的指针数组: int *p[5][95][8]



结论

我们来回顾一下:

  • 如果在使用的上下文中没有其他目的,则数组衰减到地址
  • 多维数组只是数组的数组 – 因此,“衰减”地址将承担“我有子维”的负担,
  • 尺寸数据不能存在于指针中, 除非将其提供给它

简而言之:多维数组衰减到能够理解其内容的地址。

我感觉这是一个粗略的草案,所以很快就会有一些编辑工作。

 int *ptr= l_matrix[0]; 

你可以访问像

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

毕竟二维数组也被存储为1-d。

天儿真好,

声明

 static uint8_t l_matrix[10][20]; 

已经为10个20个unit8_t位置(即200 uint8_t大小的位置)预留了存储空间,每个元素都通过计算20 x row + column来找到。

所以没有

 uint8_t (*matrix_ptr)[20] = l_matrix; 

给你你需要什么,并指向数组第一行的列零元素?

编辑:思考这个更进一步,是不是一个数组名,根据定义,一个指针? 也就是说,数组的名称是第一个元素的位置的同义词,即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]; 

已经预留了200个类型为uint8_t的连续位置。

使用以下形式的语句引用l_matrix:

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

会给你在行rowno中找到的colno'th元素的内容。

所有指针操作都会自动考虑指向的对象的大小。 – K&R第5.4节,第103页

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

HTH

干杯,

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

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

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

可悲的是,它不能修复sizeof() ,编译器似乎还没有使用这个信息,所以它仍然是一个好奇心。

你总是可以避免摆弄编译器,通过声明数组为线性,并自己做数组(索引,列)到数组索引计算。

 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)[10][20] = &l_matrix; 

你想要一个指向第一个元素的指针,所以;

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

如果你想使用负数索引,你也可以添加一个偏移量:

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

如果您的编译器给出错误或警告,您可以使用:

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

在一般情况下,指向二维数组的指针是这样声明的:

 int ***matrix_ptr = &l_matrix; 

指针定义是从“从里到外” – 或者最接近变量名称的符号来解析的,并且每一步都越来越远。 从左到右:

第三个“ ”是“指针”。 第二个“ ”是数组的第一个维度。 第一个“ ”是为数组的第二维。
您可以继续添加要添加到阵列的其他尺寸的 ”。

为了声明指向该数组的指针,不需要知道数组的维数。

[]符号是用于计算数组偏移的语法糖,但它是很好的糖:

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

没有要求二维矩阵的第二维在每个位置具有相同数量的元素。