C指向数组的指针/指针数组的歧义消除


463

以下声明之间有什么区别:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

了解更复杂的声明的一般规则是什么?


54
这是一篇有关阅读C语言中复杂声明的出色文章:unixwiz.net/techtips/reading-cdecl.html
jesper 2013年

@jesper不幸的是,该文章中缺少重要的和棘手的constvolatile限定符。
非用户

@ not-a-user这些与这个问题无关。您的评论不相关。请不要。
user64742

Answers:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

第三个与第一个相同。

一般规则是运算符优先级。当函数指针进入图片时,它可能变得更加复杂。


4
因此,对于32位系统:int * arr [8]; / *为每个指针分配8x4字节/ int(* arr)[8]; / 4字节已分配,仅一个指针* /
乔治

10
不。int * arr [8]:总共分配了8x4个字节,每个指针4个字节。int(* arr)[8]是正确的,4个字节。
Mehrdad Afshari,2009年

2
我应该重新阅读我写的内容。我的意思是每个指针4个。谢谢您的帮助!
乔治

4
第一个与最后一个相同的原因是,总是允许在声明符周围加上括号。P [N]是一个数组声明符。P(....)是函数声明符,* P是指针声明符。因此,以下内容与没有括号的情况相同(函数之一的“()”除外:int((((* p))); void((g(void))); int *(a [1]);无效(*(p()))
约翰绍布- litb

2
在您的解释中做得很好。有关运算符的优先级和关联性的深入参考,请参阅Brian Kernighan和Dennis Ritchie撰写的C语言(ANSI C第二版)的第53页。运算符( ) [ ] 从左到右关联,并且优先级高于*int* arr[8]为大小8的数组(每个元素指向一个int)和int (*arr)[8]指向大小为8的数组的指针,该数组保存整数
Mushy

267

使用K&R建议的cdecl程序。

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

它也以其他方式起作用。

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankii大多数Linux发行版都应该有一个软件包。您还可以构建自己的二进制文件。
sigjuice

遗憾的是,这里没有提到macOS。将查看是否可用,否则网站也可以。^^感谢您让我知道这一点。.随意标记NLN。
ankii

2
@ankii您可以从Homebrew(也许还有MacPorts)安装。如果这些都不符合您的口味,则可以从cdecl.org右上角的Github链接构建自己的链接(我刚刚在macOS Mojave上构建了链接)是微不足道的。然后只需将cdecl二进制文件复制到您的PATH中即可。我建议使用$ PATH / bin,因为不需要root参与像这样简单的事情。
sigjuice

哦,我还没有阅读自述文件中有关安装的小段文字。只是一些用于处理依赖项的命令和标志。.使用brew安装。:)
ankii

1
当我初读此书时,我想:“我永远不会降到这个水平”。第二天,我下载了它。
西罗Santilli郝海东冠状病六四事件法轮功

126

我不知道它是否有正式名称,但我称它为Right-Left Thingy(TM)。

从变量开始,然后向右,向左,向右...等等。

int* arr1[8];

arr1 是由8个指向整数的指针组成的数组。

int (*arr2)[8];

arr2 是指向8个整数的数组的指针(右括号为左括号)。

int *(arr3[8]);

arr3 是由8个指向整数的指针组成的数组。

这应该可以帮助您解决复杂的声明。


19
我听说过它的名称是“螺旋尺”,可以在这里找到。
Fouric

6
@InkBlend:螺旋法则不同于左右法则。在前者未能在同类个案int *a[][10],而后者成功。
legends2k 2013年

1
@dogeen我认为这个词与Bjarne Stroustrup有关:)
Anirudh Ramanathan 2013年

1
正如InkBlend和legends2k所说,这是螺旋规则,它更复杂并且在所有情况下均不起作用,因此没有理由使用它。
kotlomoy

不要忘了Lef从右到右的结合( ) [ ]和从右到左的结合* &
穆西

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

第三个不应该是:a是指向大小为8的整数数组的指针数组吗?我的意思是每个整数数组的大小都是8,对吗?
拉希尔·保罗

2
@Rushil:不,最后一个下标([5])表示内部尺寸。这意味着这(*a[8])是第一维,因此是数组的外部表示。什么内的每个元素a 是大小5的不同整数数组
zeboidlund

感谢您的第三个。我正在寻找如何编写指向数组的指针数组。
德庆

15

最后两个答案也可以从C语言的黄金法则中推论得出:

使用后声明。

int (*arr2)[8];

如果取消引用会arr2怎样?您将得到一个由8个整数组成的数组。

int *(arr3[8]);

如果从中获取元素会arr3怎样?您将获得一个指向整数的指针。

这在处理指向函数的指针时也有帮助。以sigjuice为例:

float *(*x)(void )

取消引用后会发生什么x?您将获得一个不带参数即可调用的函数。打电话时会发生什么?它将返回指向的指针float

但是,运算符优先级始终很棘手。但是,使用括号实际上也会造成混淆,因为声明是在使用之后进行的。至少对我来说,直观上arr2看起来像是由8个指向int的指针组成的数组,但实际上是相反的。只是需要一些习惯。有足够的理由总是向这些声明添加评论,如果您问我:)

编辑:示例

顺便说一句,我偶然遇到了以下情况:一个具有静态矩阵并使用指针算术查看行指针是否超出范围的函数。例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

输出:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

请注意,border的值永远不会改变,因此编译器可以对其进行优化。这与您最初可能要使用的内容不同::const int (*border)[3]声明border为指向3个整数数组的指针,只要变量存在,该整数就不会更改值。但是,该指针可以随时指向任何其他此类数组。我们希望参数具有这种行为(因为此函数不会更改任何这些整数)。使用后声明。

(ps:随时改进此示例!)



3

作为一个经验法则,右目运算符(如[]()等)采取优先于左侧的。因此,int *(*ptr)()[];将是一个指向一个函数的指针,该函数返回一个指向int的指针的数组(在括号中尽快获得正确的运算符)


没错,但它也是回教徒。您不能具有返回数组的函数。我尝试了一下:error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];在GCC 8下与$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito

1
编译器产生该错误的原因是,它会将函数解释为返回数组,这是对优先规则状态的正确解释。作为声明,它是ilegal,但合法声明int *(*ptr)();允许稍后使用诸如p()[3](或(*p)()[3])之类的表达式。
路易斯·科罗拉多,

好的,如果我理解的话,您是在谈论创建一个返回指向数组第一个元素的指针(而不是数组本身)的函数,然后再将该函数用作返回数组的指针吗?有趣的主意。我会试试看。int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }并这样称呼:foo(arr)[4];哪个应该包含arr[2][4],对吗?
卡卡休埃特·弗里托(Cacahuete Frito)

对...但是您也对,声明是回教徒。:)
路易斯·路易斯科罗拉多州

2

我认为我们可以使用简单的规则..

example int * (*ptr)()[];
start from ptr 

ptr是指向”向右..“ ”“” 的指针,现在向左走“(”出来向右“()”,因此“指向一个不带参数的函数”向左“并返回一个指针” go“向右“到数组”向左“整数”


我会稍微改善一下:“ ptr是一个名称,指的是”向右走...它是),现在向左走...它是*“指向”的指针向右走...它是),现在向左走...这是一个(出来,向右走,()所以“到一个不带参数的函数”,向右走…… []并返回一个数组,向右走;,所以向左走…… *“指向”的指针向左走…… int“整数”
卡卡胡埃特·弗里托(Cacahuete Frito)


2

这是我的解释方式:

int *something[n];

关于优先级的注意事项:数组下标运算符([])比取消引用运算符()具有更高的优先级*

因此,这里我们将应用[]before *,使该语句等效于:

int *(something[i]);

请注意声明的意义:int nummeans num是一个intint *ptrint (*ptr)Means(的值是ptr)是一个int,它ptr指向int

可以理解为((某物的第i个索引处的值)的值)是一个整数。因此,(某物第ith个索引处的值)是一个(整数指针),这使某物成为整数指针数组。

在第二个中

int (*something)[n];

为了使此语句有意义,您必须熟悉以下事实:

关于数组的指针表示的注意:somethingElse[i]等同于*(somethingElse + i)

因此,替换somethingElse(*something),我们得到*(*something + i),它是每个声明的整数。因此,(*something)给我们一个数组,它等于(指向数组的指针)


0

我想第二个声明使许多人困惑。这是一种了解它的简单方法。

让我们拥有一个整数数组,即int B[8]

让我们还有一个指向B的变量A。现在,A处的值为B,即(*A) == B。因此,A指向整数数组。在您的问题中,arr与A类似。

同样,在中int* (*C) [8],C是指向整数的指针数组的指针。


0
int *arr1[5]

在此声明中,arr1是5个指向整数的指针的数组。原因:方括号的优先级高于*(取消引用的运算符)。在这种类型中,行数是固定的(此处为5),但列数是可变的。

int (*arr2)[5]

在此声明中,arr2是指向5个元素的整数数组的指针。原因:这里()括号的优先级高于[]。在这种类型中,行数是可变的,但列数是固定的(此处为5)。


-7

在指向整数的指针中,如果指针增加,则指针将指向下一个整数。

在指针数组中,如果指针增加,它将跳转到下一个数组


在指针数组中,如果指针增加,它将跳转到下一个数组 ”,这是错误的。
ALK
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.