【问题标题】:Array of pointers to an array of fixed size指向固定大小数组的指针数组
【发布时间】:2016-05-20 14:02:56
【问题描述】:

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

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

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

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

如果我运行代码,它将引发segmentation fault。但是,如果我动态分配AB,它工作得很好。 这是为什么?

【问题讨论】:

  • 提示:检查数据类型
  • "如果我将 A 和 B 声明为动态数组..."。 C没有这样的功能。您不能“将 A 和 B 声明为动态数组”。您只能将它们声明为指针,然后手动分配内存以使它们表现为动态多维数组。这种“手动构建”的数组与内置的多维数组不兼容——它们是完全不同的两个东西。
  • 此代码正在生成 UB。 “标量的初始值设定项应为单个表达式,可选择用大括号括起来。” GCC 和 clang 接受很多糟糕的代码(作为扩展?)。使用msvc编译会报错(life example)。
  • 只要说 var C={&A, &B},哦,等等,这是 C-what?

标签: c arrays


【解决方案1】:

如果您想要一个适合现有 AB 声明的 C 声明,您需要这样做:

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};

【讨论】:

  • 对!但如果可能的话,我不会用十英尺长的杆子碰它;)
【解决方案2】:

不幸的是,那里有很多蹩脚的书/教程/老师会教你错误的东西......

忘记指针指向指针吧,它们与数组无关。期间。

还有一条经验法则:每当您发现自己使用了超过 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,取它指向的内容 - 这是一个二维数组 - 然后取该数组中索引 [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 数组。
  • 创建一个包含二维数组的结构,然后创建一个包含此类结构的数组。

【讨论】:

  • 我认为您的经验法则很有价值。 C++ 不禁止这样的构造,但它允许你避免它们。
【解决方案3】:

这条线有很多问题

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

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

&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

哎呀。实在是太丑了如果您想通过C 访问AB 的元素,那就更难看了:

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

我们必须显式取消引用C[i],然后才能索引到它指向的数组,并且由于下标运算符[] 比一元运算符* 具有更高的优先级,我们需要将*C[0] 分组到括号中.

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

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

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

好吧,这仍然很引人注目,但它与没有 typedef 的情况一样干净。

那么我们如何通过C访问AB的元素呢?

记住数组下标操作a[i]定义*(a + i);也就是说,给定一个基地址a,从该地址偏移i elements (not bytes)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] 都产生相同的(数组的地址和第一个元素的地址数组的类型总是相同的),但 types 不同,如上表所示。


  1. 指针算法考虑了指向类型的大小;如果p 包含int 对象的地址,那么p+1 会产生下一个int 对象 的地址,可能相距2 到4 个字节。

【讨论】:

  • 非常好,尤其是您关于如何声明C 以便它可以像3D 数组一样被索引的建议。
【解决方案4】:

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] = {{0}};
int **p2 = a2;

然后弹出一个关于指针分配不兼容的警告,然后他们想:“天哪!为什么这个数组变成了 Harvey Dent?”。

有些人甚至领先一步

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

然后Riddler 陷入了数组指针等价的噩梦。

永远记住数组不是指针,反之亦然。数组是一种数据类型,而指针是另一种数据类型(不是数组类型)。几年前,C-FAQ 已经解决了这个问题:

说数组和指针是“等价的”意味着它们既不是相同的,也不是可以互换的。这意味着数组和指针算法被定义为可以方便地使用指针来访问数组或模拟数组。换句话说,正如 Wayne Throop 所说,它是“指针算法和数组索引 [that] 在 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](二维数组是一维数组的数组),因此指向其第一个元素的指针类型为int(*)[5](指向数组的指针),它与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;

现在来到你的sn-p

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

&A&B 的类型为 int(*)[5][5]Cint*** 类型,它不是一个数组。由于您想让C 保存数组AB 的地址,因此您需要将C 声明为两个int(*)[5][5] 类型元素的数组。应该这样做

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

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

在这种情况下,您必须将AB 声明为int **。在这种情况下,两者都是指针,而不是数组。 Cint *** 类型,所以它可以保存int** 类型数据的地址。请注意,在这种情况下,声明 int*** C = {&A, &B}; 应该是

  int*** C = &A;

如果是int*** C = {&A, &B};,程序的行为要么是未定义的,要么是实现定义的。

C11:5.1.1.3(P1):

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

阅读this post了解更多解释。

【讨论】:

  • @GauravJain;使用 C99 编译该代码,您将得到 warnings
  • 好的...但是它是怎么起作用的呢?当我用 gcc 编译时发生了什么巧合?
  • @GauravJain;它们都是用 GCC 编译的,唯一的区别是你没有使用 C99 标志,因此你的代码默认是在 C89 模式下编译的。这个旧版本的 C 可能不会产生任何警告。
【解决方案5】:

数组与 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));
}

【讨论】:

  • 数组与多维或其他方面的指针根本完全不同。此外,数组的名称表示数组,而不是任何指针。重要的是不要将其与大多数(但不是全部)上下文中数组值衰减指向指针的事实相混淆。
  • 另外,给定int A[5][5],当A确实衰减到一个指针时,该指针的类型是int (*)[5],而不是int *
  • 当你想要二维数组时不要像这样分配数据,这只是一种不好的做法,有很多缺点,没有好处。您想要制作这样的结构的唯一原因是每个维度都需要单独的尺寸。这不是这里的情况。
  • (actually int *[5]) 不正确。这是一个指针数组,而不是指向数组的指针。您计算的偏移量不正确。 A[x + 5 * y] 产生的结果与 A[x][y] 不同
  • 这个答案没有回答问题以及@JohnBollinger 和@2501 指出的误导。另外:如果A 被声明为int A[5][5],那么A通常表示第一个元素的地址:不。它始终表示A 是一个由5 个ints 组成的数组组成的数组。不多也不少。
【解决方案6】:

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

当你声明像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

【讨论】:

  • 从 OP 的角度来看,int **C 的问题是它丢失了 2D 索引信息。无论如何,答案都很好。
  • 数组和指针之间没有等价性。它们是相关联的,但根本不是一回事。另外,给定int A[5][5]A 衰减的指针类型是int (*)[5],而不是int *
  • 您可以考虑改写vector of pointers to arrays,因为在后续语句中您有两个指针数组。只是没有达到预期的效果。
  • 很抱歉,您的回答具有误导性。您说:结果对象A 是指向此内存块开始的指针。它的类型是int *,而不是int **。这是完全错误的。对象A 是一个数组类型。在某些情况下,它将转换为指向它的第一个元素的指针。 A 的类型是 int[5][5]。转换后它的类型为int (*)[5]。同样:如果你想要一个指向数组的指针向量,你想将你的变量声明为:int *A[5], *B[5];:不。它将声明指针数组而不是向量指向数组的指针.
  • int *A[5], *B[5]; 你为什么说这些是指向数组的指针的向量(在 C 中没有任何意义,但我们假设你的意思是数组),而它们显然不是?
【解决方案7】:

虽然数组和指针密切相关,但它们根本不是一回事。人们有时对此感到困惑,因为在大多数情况下,数组值衰减为指针,并且因为数组表示法可以在函数原型中用于声明实际上是指针的参数。此外,许多人认为的数组索引表示法实际上结合了指针算术和解引用,因此它对指针值和数组值同样适用(因为数组值衰减为指针)。

鉴于声明

int A[5][5];

变量A 指定由五个int 组成的五个数组的数组。这会在它衰减的地方衰减到 int (*)[5] 类型的指针——即指向 5 个数组的指针 int。另一方面,指向整个多维数组的指针的类型为int (*)[5][5](指向由5 个数组组成的数组的指针int),这与int ***(指向指向@ 的指针的指针)完全不同。 987654329@)。如果你想声明一个指向多维数组的指针,那么你可以这样做:

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

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

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

已添加:

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

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 *)

这是数组索引如何工作的基础,对于理解何时分配内存至关重要。

【讨论】:

    【解决方案8】:

    你应该像这样声明第三个数组

    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 
    

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

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

    至于这份声明

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

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

    因此编译器发出一条消息。

    【讨论】:

    • 我认为这些都没有涵盖 OP 实际尝试做的事情(3D 索引)。
    • C 有两个元素,而不是 5 个。
    【解决方案9】:

    我非常相信使用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 too 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()
    

    【讨论】:

      猜你喜欢
      • 2011-12-04
      • 1970-01-01
      • 2015-05-24
      • 2010-12-21
      • 2011-11-28
      • 2014-11-10
      • 1970-01-01
      • 2019-05-14
      相关资源
      最近更新 更多