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


119

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

static uint8_t l_matrix[10][20];

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

我收到各种错误,例如:

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


1
@ JohannesSchaub-litb不再存在。(我如何再次查看它??我知道低重复率的成员可以查看它,但我忘记了如何...)
Mateen Ulhaq 2011年

Answers:


139

在这里,您想指向数组的第一个元素

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


+1-关于int(*)[] [20]细分的详细信息-无法在C ++中做到这一点
Faisal Vali 2009年

@litb,很抱歉,这是错误的,因为您的解决方案未为阵列提供任何存储分配。
罗布·威尔斯

2
@Rob,我不太了解你。在所有这些情况下,存储都由数组l_matix本身提供。指向它们的指针从它们声明在的任何地方以as(堆栈,静态数据段等)进行存储。
Johannes Schaub-litb

只是好奇为什么我们需要l_matrix的“&”地址?
电子版

1
@Sohaib-不,仅创建一个指针。您可能将其与混淆了uint8_t *d[20],后者创建了一个由3个指向uint8_t的指针组成的数组,但是在这种情况下不起作用。
帕洛阿尔托

27

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

数组不是指针!

首先(已经讲到足够了),数组不是指针。相反,在大多数使用中,它们“衰减”到其第一个元素的地址,该地址可以分配给指针:

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 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 分配给指针却 可以分配给指针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

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

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



结论

我们来复习:

  • 如果数组在使用的上下文中没有其他用途,则会衰减为地址
  • 多维数组只是数组的数组-因此,“已腐烂”地址将承担“我有子维”的负担
  • 除非将数据赋予指针,否则维度数据不能存在于指针中。

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


1
答案的第一部分很棒,但是第二部分则不是。这是不正确的:int *p1 = &(a[0]); // RIGHT !,实际上与int *p1 = a;
2501 2016年

@ 2501感谢您发现该错误,我已纠正它。我不能肯定地说为什么定义这个“规则”的例子也违背了它。值得重申的是,仅仅因为两个实体可以解释为指针并产生相同的值,并不意味着它们具有相同的含义。
超级猫

7

int *ptr= l_matrix[0];

您可以像访问

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

所有二维数组也都存储为1-d。


5

G'day,

报关单

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;

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

编辑:再考虑一下这个问题,根据定义,数组名称不是指针吗?也就是说,数组的名称是第一个元素的位置的同义词,即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标准的定义。

高温超导

干杯,


1
uint8_t(* matrix_ptr)[] [20] <<必须保留第一括号,正确的是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(),编译器似乎还没有使用该信息,因此它仍然是一种好奇。


1
这个答案是令人误解的:这不会使参数成为固定大小的数组,而是一个指针。static 10是某种保证至少存在10个元素,这再次意味着大小是不确定的。
bluss

1
@bluss问题是关于指针的,所以我看不到用指针回答(按引用指出)是如何引起误解的。从函数的角度来看,数组是固定大小的,因为未定义对超出这些范围的元素的访问。
Kornel

我不认为超过10的访问权限是未定义的,我看不到任何指示信息。
bluss 2016年

这个答案似乎表明,如果没有关键字static,数组将不会通过引用传递,这是不正确的。无论如何,数组都是通过引用传递的。最初的问题询问了一个不同的用例-在同一函数/名称空间中使用辅助指针访问2D数组的元素。
帕洛阿尔托

4

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

}

这就是编译器本来应该做的。


1
无论如何,这就是C编译器所做的。C并没有真正的“数组”概念,[]符号只是指针算术的语法糖
Ken Keenan 2009年

7
这种解决方案的缺点是永远找不到正确的方法。
Craig McQueen

2

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

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

有效...


1

您可以这样做:

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

1
这不会占用10 * 20字节的RAM吗?(安装在微控制器上)
Dill

它会占用4个字节或您的盒子中指针大的任何大小。但是请记住,如果您有此索引,则必须使用matrix_ptr [0] [x] [y]或(* matrix_ptr)[x] [y]进行索引。这是“指向二维数组的指针”的直接和逐词解释:p
Johannes Schaub-litb

谢谢litb,我忘了提到如何访问它。没有必要编辑我的答案,因为您的答案做得很出色:)
Nick Dandoulakis 2009年

那么,这是否占用10 * 20字节的RAM?
丹尼尔(Danijel)'18年

@Danijel,因为它是一个指针二维数组,它只会占据4个字节或任何大小的指针是在你的箱子,即16位,32位,64位等
尼克Dandoulakis

1

您需要一个指向第一个元素的指针。

static uint8_t l_matrix[10][20];

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

0

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

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,因此答案应使用相同的语言。请注意标签。
2501年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.