创建指向二维数组的指针

Posted

技术标签:

【中文标题】创建指向二维数组的指针【英文标题】:Create a pointer to two-dimensional array 【发布时间】:2009-06-27 13:20:43 【问题描述】:

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

static uint8_t l_matrix[10][20];

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

我收到各种错误,例如:

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

【问题讨论】:

@JohannesSchaub-litb 那已经不存在了。 (我如何再次查看它...?我知道低代表成员可以查看它,但我忘记了如何...) @muntoo:这是它的副本:gist.github.com/sharth/ede13c0502d5dd8d45bd 【参考方案1】:

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

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 语言中的 pointer/array world,这引起了很多混乱。


编辑

在此处查看其他一些答案,因为评论字段太短而无法在此处进行。提出了多种替代方案,但没有显示它们的行为方式。以下是他们的做法

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

如果您修复错误并添加地址运算符&,如下面的sn-p

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 的警告),如果你不能改变数组类型,这是最后一个救你的选择:)

【讨论】:

+1 - 关于 int (*)[][20] 细分的好信息 - 在 C++ 中无法做到这一点 @litb,很抱歉,这是错误的,因为您的解决方案没有为阵列提供任何存储分配。 @Rob,我不太了解你。所有这些情况下的存储都是由数组 l_matix 本身提供的。指向它们的指针从它们被声明的位置和作为(堆栈,静态数据段,...)获取存储。 只是好奇为什么我们需要 l_matrix 的“&”地址? @Sohaib - 不,它只创建一个指针。您可能将它与 uint8_t *d[20] 混淆了,后者创建了一个包含 3 个指向 uint8_t 的指针的数组,但在这种情况下不起作用。【参考方案2】:

完全理解这一点,您必须掌握以下概念:

数组不是指针!

首先(并且已经被充分宣扬),数组不是指针。相反,在大多数用途中,它们“衰减”到它们的第一个元素的地址,该地址可以分配给一个指针:

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]' 级别,我们已经处于最高维度,因此就程序而言,我们只是在查看一个普通数组。

你可能会问:

如果数组是多维的,为什么要为它创建指针?

最好这样想:

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

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

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]



结论

让我们回顾一下:

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

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

【讨论】:

答案的第一部分很好,但第二部分不是。这是不正确的:int *p1 = &(a[0]); // RIGHT !,实际上它与int *p1 = a;相同 @2501 感谢您发现该错误,我已更正。我不能肯定地说为什么定义这个“规则”的例子也违背了它。值得重申的是,仅仅因为两个实体可以被解释为指针并产生相同的值,并不意味着它们具有相同的含义。 只有编译器对数组和指针的看法不同。在运行时,数组退化为常量指针。【参考方案3】:

int *ptr= l_matrix[0];

你可以访问像

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

毕竟二维数组也存储为一维数组。

【讨论】:

*(p+k) 实际上就是p[k]【参考方案4】:

生日,

声明

static uint8_t l_matrix[10][20];

为 10 行 20 个 unit8_t 位置预留存储空间,即 200 个 uint8_t 大小的位置, 通过计算 20 x 行 + 列来找到每个元素。

所以没有

uint8_t (*matrix_ptr)[20] = l_matrix;

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

编辑: 再想一想,根据定义,数组名称不就是指针吗?即数组名是第一个元素位置的同义词,即l_matrix[0][0]?

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

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

干杯,

【讨论】:

uint8_t (*matrix_ptr)[][20] 【参考方案5】:

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

int l_matrix[10][20];

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


int main(void) 
    test(l_matrix);

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

遗憾的是,它没有修复 sizeof(),编译器似乎还没有使用该信息,所以它仍然是一个好奇心。

【讨论】:

这个答案具有误导性:这不会使参数成为固定大小的数组,它仍然是一个指针。 static 10 是一种保证至少存在 10 个元素,这再次意味着大小不固定。 @bluss 问题是关于指针的,所以我看不出用指针回答(注意 by reference)有什么误导性。从函数的角度来看,数组是固定大小的,因为对超出这些边界的元素的访问是未定义的。 我不认为超过 10 的访问是未定义的,我看不到任何表明这一点的东西。 这个答案似乎暗示没有关键字static,数组将不会通过引用传递,这是不正确的。无论如何,数组都是通过引用传递的。最初的问题询问了一个不同的用例 - 使用同一函数/命名空间中的补充指针访问 2D 数组的元素。【参考方案6】:

您始终可以通过将数组声明为线性数组并自行对数组索引进行 (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


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

【讨论】:

这就是 C 编译器所做的。 C 并没有真正的“数组”概念——[] 符号只是指针算术的语法糖 这种解决方案的缺点是永远找不到正确的方法。【参考方案7】:

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

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

成功了……

【讨论】:

【参考方案8】:

你可以这样做:

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

【讨论】:

这不是占用 10 * 20 字节的内存吗? (我在微控制器上) 它将占用 4 字节或任何大小的指针在您的框中很大。但是请记住,如果您有这个,则必须使用 matrix_ptr[0][x][y] 或 (*matrix_ptr)[x][y] 进行索引。这是“指向二维数组的指针”的直接和逐字解释:p 谢谢 litb,我忘了说如何访问它。没有必要编辑我的答案,因为你的答案做得很好:) 那么,这是否占用 10 * 20 字节的 RAM? @Danijel,因为它是一个指向二维数组的指针,所以它将只占用 4 个字节或指针在您的框中的任何大小,即 16 位、32 位、64 位,等【参考方案9】:

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

static uint8_t l_matrix[10][20];

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

【讨论】:

【参考方案10】:

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

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;

【讨论】:

你好。这个问题被标记为c,所以答案应该是相同的语言。请注意标签。

以上是关于创建指向二维数组的指针的主要内容,如果未能解决你的问题,请参考以下文章

怎么让一个二维指针指向一个二维数组

C语言指针指向一维数组与二维数组?

二维数组名不能赋值给二级指针- -

C语言指向二维数组的指针

指向 C++ 中动态分配的二维数组中的一行的指针

C语言 指向二维数组的指针