创建指向二维数组的指针

时间:2009-06-27 13:20:43

标签: c arrays pointers

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

static uint8_t l_matrix[10][20];

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

我遇到各种错误:

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

10 个答案:

答案 0 :(得分:127)

这里你想要指向数组的第一个元素

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;

如果您修复错误并添加地址操作符&,请参阅以下代码段

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,而是{{ 1}}

uint8_t[20]

以下是一个不错的选择

uint8_t *matrix_ptr = l_matrix; // fail

您可以使用

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

它的好处是可以保留外部尺寸的大小。所以你可以在上面应用sizeof

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

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

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 ,但是如果您继续进行别名分析和其他积极的优化,某些编译器可能会做出可能会破坏该代码的假设。话虽如此,我从来没有遇到过失败的编译器(但是我再也没有在实际代码中使用过这种技术),甚至C FAQ都包含了这种技术(警告其UB'ness) ),如果您无法更改数组类型,这是保存您的最后一个选项:)

答案 1 :(得分:23)

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

数组不是指针!

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

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普通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]

结论

让我们回顾一下:

  • 如果在使用的上下文中没有其他目的,则数组会衰减到地址
  • 多维数组只是数组的数组 - 因此,'衰变'地址将承担“我有子维度”的负担
  • 尺寸数据不能存在于指针中,除非您将其赋予

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

答案 2 :(得分:7)

int *ptr= l_matrix[0];

您可以像

一样访问
*p
*(p+1)
*(p+2)

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

答案 3 :(得分:5)

天儿真好,

声明

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;

为您提供所需内容并指向数组第一行的第0列元素?

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

欢呼声,

答案 4 :(得分:5)

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

int l_matrix[10][20];

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

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

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

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

答案 5 :(得分: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

}

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

答案 6 :(得分:1)

你可以这样做:

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

答案 7 :(得分:1)

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

static uint8_t l_matrix[10][20];

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

答案 8 :(得分: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;

答案 9 :(得分:0)

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

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() {
   char balance[5][100] = { {"Subham"},{"Messi"} };//The multidimentional array...

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

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

  return 0;
}

输出是:

Subham
Messi

那就做到了......