指向固定大小数组的指针数组

时间:2016-05-20 14:02:56

标签: c arrays

我尝试将两个固定大小的数组分配给指向它们的指针数组,但编译器警告我,我不明白为什么。

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

此代码编译时出现以下警告:

  

警告:从不兼容的指针类型初始化[默认启用]

如果我运行代码,它将引发segmentation fault。但是,如果我动态分配AB,它就可以正常工作。 为什么会这样?

9 个答案:

答案 0 :(得分:25)

如果您希望声明C符合AB的现有声明,则需要这样做:

int A[5][5];
int B[5][5];
int (*C[])[5][5] = {&A, &B};

C的类型读作“ C是指向int [5][5]数组”的指针数组。由于无法分配整个数组,因此需要指定一个指向数组的指针。

通过此声明,(*C[0])[1][2]正在访问与A[1][2]相同的内存位置。

如果你想要更清晰的C[0][1][2]语法,那么你需要做其他人所说的并动态分配内存:

int **A;
int **B;
// allocate memory for A and each A[i]
// allocate memory for B and each B[i]
int **C[] = {A, B};

你也可以使用Vlad来自莫斯科建议的语法来做到这一点:

int A[5][5];
int B[5][5];
int (*C[])[5] = {A, B};

C声明读作“ C是指向int [5]数组”的指针数组。在这种情况下,C的每个数组元素都是int (*)[5]类型,类型int [5][5]的数组可以衰减为此类型。

现在,您可以使用C[0][1][2]访问与A[1][2]相同的内存位置。

此逻辑也可以扩展到更高的维度:

int A[5][5][3];
int B[5][5][3];
int (*C[])[5][3] = {A, B};

答案 1 :(得分:19)

不幸的是,有很多糟糕的书籍/教程/老师会教你错误的东西....

忘掉指针到指针,它们与数组无关。周期。

另外,作为经验法则:每当您发现自己使用超过2级间接时,很可能意味着您的程序设计存在根本缺陷,需要从头开始重新制作。

要做到这一点,你必须这样做:

指向数组int [5][5]的指针称为数组指针,并声明为int(*)[5][5]。例如:

int A[5][5];
int (*ptr)[5][5] = &A;

如果你想要一个数组指针数组,它将是int(*[])[5][5]类型。例如:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};

正如你所知,这段代码看起来不必要复杂 - 而且确实如此。访问单个项目会很麻烦,因为您必须键入(*arr[x])[y][z]。含义:“在数组指针数组中取数组指针编号x,取其指向的内容 - 这是一个2D数组 - 然后在该数组中取索引[y] [z]项。”

发明这样的结构只是疯狂而我不推荐。我想通过使用普通数组指针可以简化代码:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};
int (*ptr)[5][5] = arr[0];
...
ptr[x][y][z] = 0;

然而,这仍然是一些复杂的代码。 完全考虑不同的设计! 示例:

  • 制作3D阵列。
  • 创建包含2D数组的结构,然后创建此类结构的数组。

答案 2 :(得分:14)

有很多错误
int*** C = {&A, &B};

您正在声明单个指针C,但您告诉它指向多个对象;这是行不通的。您需要做的是将C声明为指向这些数组的指针的数组

&A&B的类型都是int (*)[5][5],或“指向int的5元素数组的5元素数组的指针”;因此,C的类型必须是“指向int的5元素数组的5元素数组的指针数组”,或

int (*C[2])[5][5] = { &A, &B };

读作

      C           -- C is a
      C[2]        -- 2-element array of
     *C[2]        -- pointers to
    (*C[2])[5]    -- 5-element arrays of
    (*C[2])[5][5] -- 5-element arrays of
int (*C[2])[5][5] -- int

呸。这真是太可恶了。如果您想要访问ABC的元素,则会更加丑陋:

int x = (*C[0])[i][j]; // x = A[i][j]
int y = (*C[1])[i][j]; // y = B[i][j]

在我们可以索引它指向的数组之前,我们必须明确地取消引用C[i],并且由于下标运算符[]的优先级高于一元*运算符,我们需要parens中的*C[0]组。

我们可以稍微清理一下。除非它是sizeof或一元&运算符的操作数(或者是用于初始化声明中的另一个数组的字符串文字),否则表达式类型为“ N - T的元素数组“将被转换(”衰减“)为”指向T的指针“的表达式,表达式的值将是数组的第一个元素。

表达式AB具有类型int [5][5]或“5元素数组int的5元素数组”。根据上面的规则,两个表达式都“衰减”为“指向int的5个元素数组的指针”或int (*)[5]的表达式。如果我们使用AB而不是&A&B初始化数组,那么我们需要一个指向int的5元素数组的指针数组,或

int (*C[2])[5] = { A, B };

好吧,那仍然非常刺眼,但是这很干净,因为没有typedef就可以了。

那么我们如何通过A访问BC的元素?

请记住,数组下标操作a[i] 已定义*(a + i);也就是说,给定一个基地址a,从该地址偏移i 元素不是字节 1 取消引用结果。这意味着

*a == *(a + 0) == a[0]

因此,

*C[i] == *(C[i] + 0) == C[i][0]

把这一切放在一起:

C[0] == A                      // int [5][5], decays to int (*)[5]
C[1] == B                      // int [5][5], decays to int (*)[5]

*C[0] == C[0][0] == A[0]       // int [5], decays to int *
*C[1] == C[1][0] == B[0]       // int [5], decays to int *

C[0][i] == A[i]                // int [5], decays to int *
C[1][i] == B[i]                // int [5], decays to int *

C[0][i][j] == A[i][j]          // int
C[1][i][j] == B[i][j]          // int

我们可以将C 编入索引,好像它是int的3D数组,比(*C[i)[j][k]更清晰。

此表也可能有用:

Expression        Type                "Decays" to       Value
----------        ----                -----------       -----
         A        int [5][5]           int (*)[5]       Address of A[0]
        &A        int (*)[5][5]                         Address of A
        *A        int [5]              int *            Value of A[0] (address of A[0][0])
      A[i]        int [5]              int *            Value of A[i] (address of A[i][0])
     &A[i]        int (*)[5]                            Address of A[i]
     *A[i]        int                                   Value of A[i][0]   
   A[i][j]        int                                   Value of A[i][j]   

请注意,A&AA[0]&A[0]&A[0][0]都会产生相同的(地址)一个数组和数组的第一个元素的地址总是相同的,但类型是不同的,如上表所示。

<小时/>

  1. 指针算法考虑指向类型的大小;如果p包含int对象的地址,则p+1会生成下一个int对象的地址,该地址可能为2到4字节远。

答案 3 :(得分:9)

C初学者的一个常见误解是他们只是假设指针和数组是等价的。那是完全错误的。

初学者在看到像

这样的代码时会感到困惑
int a1[] = {1,2,3,4,5};
int *p1 = a1;            // Beginners intuition: If 'p1' is a pointer and 'a1' can be assigned
                         // to it then arrays are pointers and pointers are arrays.

p1[1] = 0;               // Oh! I was right
a1[3] = 0;               // Bruce Wayne is the Batman! Yeah.

现在,初学者已经证实数组是指针,指针是数组,所以他们做了这样的实验:

int a2[][5] = enter image description here;
int **p2 = a2;

然后会出现一个警告,指出不兼容的指针分配,然后他们会想:“我的天啊!为什么这个阵列变成Harvey Dent?”。

有些人甚至提前一步

int a3[][5][10] = {{{0}}};
int ***p3 = a3;             // "?"

然后Riddler来到他们的数组指针等效的噩梦。

{{0}}

永远记住数组不是指针,反之亦然。数组是数据类型,指针是另一种数据类型(不是数组类型)。几年前在C-FAQ

中已经解决了这个问题
  

说数组和指针是“等价的”意味着它们既不相同也不可互换。这意味着定义了数组和指针算法,以便可以方便地使用指针来访问数组或模拟数组。换句话说,正如Wayne Throop所说,它是“指针算术和数组索引[在] C中相同,指针和数组是不同的。”

现在永远记住几个重要的数组规则,以避免这种混淆:

  • 数组不是指针。指针不是数组。
  • 当在表达式中使用时,数组被转换为指向其第一个元素的指针,除非sizeof&操作符的操作数。
  • 指针算术数组索引是相同的。
  • 指针和数组不同。
  • 我说“指针不是数组,反之亦然”。

现在你有了规则,你可以在

中得出结论
int a1[] = {1,2,3,4,5};
int *p1 = a1;

a1是一个数组,在声明int *p1 = a1;中,它转换为指向其第一个元素的指针。其元素的类型为int,则指向其第一个元素的指针的类型为int *,与p1兼容。

int a2[][5] = {{0}};
int **p2 = a2;

a2是一个数组,在int **p2 = a2;中它衰减指向其第一个元素。它的元素类型为int[5](2D数组是一维数组的数组),因此指向其第一个元素的指针的类型为int(*)[5](指向数组的指针),它与类型{{不兼容1}}。它应该是

int **

同样适用于

int (*p2)[5] = a2;

int a3[][5][10] = {{{0}}}; int ***p3 = a3; 的元素属于a3类型,指向其第一个元素的指针属于int [5][10]类型,但int (*)[5][10]属于p3类型,因此为了使它们兼容,它应该是

int ***

现在来看你的片段

int (*p3)[5][10] = a3;

int A[5][5]; int B[5][5]; int*** C = {&A, &B}; &A属于&B类型。 int(*)[5][5]的类型为C,它不是数组。由于您希望int***保留数组CA的地址,因此您需要将B声明为两个C类型的数组元素。这应该是

int(*)[5][5]
  

但是,如果我动态分配A和B,它就可以正常工作。这是为什么?

在这种情况下,您必须已将int (*C[2])[5][5] = {&A, &B}; A声明为B。在这种情况下,两者都是指针,而不是数组。 int **的类型为C,因此它可以包含int ***类型数据的地址。请注意,在这种情况下,声明int**应为

int*** C = {&A, &B};

如果是 int*** C = &A; ,程序的行为将是未定义的或实现定义的。

C11:5.1.1.3(P1):

  

如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合要求的实现应生成至少一条诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实施定义的

阅读this post以获得进一步说明。

答案 4 :(得分:8)

数组与C中的多维指针不同。数组的名称在大多数情况下被解释为包含它的缓冲区的地址,无论您如何对其进行索引。如果A被声明为int A[5][5],那么A通常意味着第一个元素的地址,即它被有效地解释为int *(实际上int *[5] }),而不是int **。地址的计算恰好需要两个元素:A[x][y] = A + x + 5 * y。这样做A[x + 5 * y]很方便,它不会将A提升为多维缓冲区。

如果你想在C中使用多维指针,你也可以这样做。语法非常相似,但需要更多设置。有几种常见的方法可以做到。

使用单个缓冲区:

int **A = malloc(5 * sizeof(int *));
A[0] = malloc(5 * 5 * sizeof(int));
int i;
for(i = 1; i < 5; i++) {
    A[i] = A[0] + 5 * i;
}

每行都有一个单独的缓冲区:

int **A = malloc(5 * sizeof(int *));
int i;
for(i = 0; i < 5; i++) {
    A[i] = malloc(5 * sizeof(int));
}

答案 5 :(得分:7)

您对数组和指针的等价性感到困惑。

当您声明类似A[5][5]的数组时,因为您已声明了两个维度,C将连续为25个对象分配内存。也就是说,内存将按如下方式分配:

A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ...

结果对象A是指向此内存块开头的指针。它的类型为int *,而不是int **

如果需要指向数组的指针向量,则需要将变量声明为:

int   *A[5], *B[5];

那会给你:

A0, A1, A2, A3, A4

所有类型int*,您必须使用malloc()或其他任何内容填充。

或者,您可以将C声明为int **C

答案 6 :(得分:7)

虽然数组和指针紧密相关,但它们完全没有相同之处。人们有时会对此感到困惑,因为在大多数情况下,数组值衰减指针,并且因为数组表示法可以在函数原型中用于声明实际上是指针的参数。此外,许多人认为数组索引表示法确实执行指针算法和解除引用的组合,因此它对指针值和数组值同样有效(因为数组值会衰减为指针)。

鉴于声明

int A[5][5];

变量A指定一个包含五个int的五个数组的数组。这会衰减衰落到int (*)[5]类型的指针 - 也就是指向5 int数组的指针。另一方面,指向整个多维数组的指针具有类型int (*)[5][5](指向5个int的5个数组的数组的指针),它与int ***完全不同(指针)指向int的指针。如果你想声明一个指向多维数组的指针,那么你可以这样做:

int A[5][5];
int B[5][5];
int (*C)[5][5] = &A;

如果你想声明一个这样的指针数组,那么你可以这样做:

int (*D[2])[5][5] = { &A, &B };

加了:

这些区别以各种方式发挥作用,其中一些更重要的是数组值衰减到指针的上下文,以及与之相关的上下文。其中最重要的一个是当值是sizeof运算符的操作数时。鉴于上述声明,以下所有关系表达式都计算为1(true):

sizeof(A)       == 5 * 5 * sizeof(int)
sizeof(A[0])    == 5 * sizeof(int)
sizeof(A[0][4]) == sizeof(int)
sizeof(D[1])    == sizeof(C)
sizeof(*C)      == sizeof(A)

此外,这些关系表达式可能(但不能保证)评估为1:

sizeof(C)       == sizeof(void *)
sizeof(D)       == 2 * sizeof(void *)

这是数组索引如何工作的基础,也是了解何时分配内存所必需的。

答案 7 :(得分:3)

您应该声明第三个数组,如

int A[5][5];
int B[5][5];
int ( *C[] )[N][N] = { &A, &B };

是指向二维数组的指针数组。

例如

#include <stdio.h>

#define N   5

void output( int ( *a )[N][N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", ( *a )[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  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 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N][N];
    T C[] = { &A, &B };
*/

    int ( *C[] )[N][N] = { &A, &B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

程序输出

 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 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

或喜欢

int A[5][5];
int B[5][5];
int ( *C[] )[N] = { A, B };

是指向二维数组的第一个元素的指针数组。

例如

#include <stdio.h>

#define N   5

void output( int ( *a )[N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", a[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  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 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N];
    T C[] = { A, B };
*/

    int ( *C[] )[N] = { A, B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

程序输出与上面相同

 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 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

取决于您将如何使用第三个数组。

使用typedef(在演示程序中显示为注释)简化了数组的定义。

至于此声明

int*** C = {&A, &B};

然后在左侧声明了一个类型为int ***的指针,它是一个标量对象,而在右侧则有一个具有不同类型int ( * )[N][N]的初始值设定项列表。

因此编译器会发出消息。

答案 8 :(得分:-1)

我非常相信使用typedef

#define SIZE 5

typedef int  OneD[SIZE]; // OneD is a one-dimensional array of ints
typedef OneD TwoD[SIZE]; // TwoD is a one-dimensional array of OneD's
                         // So it's a two-dimensional array of ints!

TwoD a;
TwoD b;

TwoD *c[] = { &a, &b, 0 }; // c is a one-dimensional array of pointers to TwoD's
                           // That does NOT make it a three-dimensional array!

int main() {
    for (int i = 0; c[i] != 0; ++i) { // Test contents of c to not go to far!
        for (int j = 0; j < SIZE; ++j) {
            for (int k = 0; k < SIZE; ++k) {
//              c[i][j][k] = 0;    // Error! This proves it's not a 3D array!
                (*c[i])[j][k] = 0; // You need to dereference the entry in c first
            } // for
        } // for
    } // for
    return 0;
} // main()