C声明符理解

C declarator understanding

我正在阅读 C99 ISO 标准中的声明符,但我很难理解以下段落:

5 If, in the declaration ”T D1”, D1 has the form

    identifier

then the type specified for ident is T.

6 If, in the declaration “T D1”, D1 has the form

    ( D )

then ident has the type specified by the declaration “T D”. Thus, a declarator in parentheses is identical to the unparenthesized declarator, but the binding of complicated declarators may be altered by parentheses.

在引用的定义中,T 是一种类型(例如 intdoublestruct foo 或任何 typedef 名称),并且D1 是声明符。

声明符可能非常复杂,但是由少量的简单步骤构成的。他们提到它是归纳定义的,这是说它具有递归定义的另一种方式。

最简单的声明符就是一个标识符名称,例如xfoo。所以 T 可能是 intD1 可能是 x.

然后继续说声明符可以用括号括起来,例如(x)((x)) 等。这些都是退化的情况,都等同于 x,但有时需要括号才能产生所需的分组。例如,声明符 *x[10](*x)[10] 的意思完全不同。前者相当于*(x[10]),是一个指针数组,而后者是一个指向数组的指针。

还有更多内容(数组、指针、函数等),但这涵盖了问题中引用的部分。

你遗漏了一个重要的前一段:

4 In the following subclauses, consider a declaration

    T D1

where T contains the declaration specifiers that specify a type T (such as int) and D1 is a declarator that contains an identifier ident. The type specified for the identifier ident in the various forms of declarator is described inductively using this notation.

因此,当我们到达第 5 段和第 6 段时,我们知道我们正在考虑的声明中包含一些我们标记为 ident 的标识符。例如,在 int foo(void) 中,identfoo.

第5段说,如果声明“T D1”只是“T ident”,则声明ident 类型为 T.

第6段说,如果声明“T D1”只是“T (ident)”,它还将 ident 声明为 T.

类型

这些只是为声明的递归规范建立基本情况。条款 6.7.5.1 继续说,如果声明“T D1”是“T * some-qualifiers D” 和没有 * 和限定符的相同声明,“T D” 将声明 ident 为“ some-derived-type T”(如“T 的数组”或“指向 T 的指针”),然后使用 * 并且限定符声明 ident* 为“some-derived-type some-qualifiers 指向 T” 的指针。

例如,int x[3]声明x为“3个int的数组”,所以6.7.5.1中的这条规则告诉我们“int * const x[3]声明x 是“指向 int 的 3 个 const 指针的数组”——它采用之前必须派生的“3 的数组”并将“const 指针指向”附加到它。

类似地,条款 6.7.5.2 和 6.7.5.3 告诉我们将数组和函数类型附加到带有方括号(用于下标)和后缀圆括号的声明符。

long int *ident[4];(array (4) of pointer to long int) long int是说明符列表,*ident[4]是声明者。 您可以在不改变语义的情况下将声明符放在括号中: long int (*ident[4]);,但在更复杂的声明中,例如 long int (*ident[4])[5];array (4) of pointer to array (5) of long int),括号会影响绑定,因为没有它们,long int *ident[4][5]; 将被解释为 long int 指针的数组 (5) 的数组 (4).

之所以这样工作,是因为声明符部分可以递归地包含另一个声明符,依此类推。

来自旧的C手册(在后来的标准化C中变得更间接,但基本原理是一样的):

declarator:
   identifier
   * declarator
   declarator ( )
   declarator [ constant-expression opt ]
   ( declarator )

换句话说,C 声明符是一种为 pointer-to/function-returning 添加前缀的方法/array-of 到一些说明符列表(例如,long int),这些可以组合起来创建一个链。

通常,在创建该链时,后缀(()=函数返回,[]=数组)比 */ 绑定得更紧pointer-to 前缀。您可以使用括号来覆盖它并强制 * (或其中的几个)绑定 now 而不会被 []/() 压倒后缀.

例如:

 long int *ident[4][5]; //array (4) of array (5) of pointer to long int
 //the suffixes win over the `*` prefix


 long int (*ident[4])[5]; //array (4) of pointer to array (5) of long int
 //the parens force `pointer to` right after `array (4)` 
 //without letting the `[]` suffix overpower the pointer-to/`*` declarator prefix

P.S.:

  1. C 不允许函数数组和函数返回函数或数组。这些在语法中都可以很好地表达,但 C 的语义检查会希望你这样做,但那里有一个 pointer-to link 来防止这些。
  2. 如果你想玩这些
  3. cdecl.org 可能会很有帮助,甚至可能更有帮助,因为它不进行所说的语义检查,因此甚至可以让你像 int foo[]()(); (数组函数返回函数返回 int),这很好地演示了语法的工作原理,即使 C 编译器会拒绝它们。

...binding of complicated declarators

这是规范中非常好的提示。

规则本身真的很难分析,因为它是递归的。 declarationdeclarator 的关系和部分也是相关的。

结果是:

  • ()[]是最里面的direct-declarator部分,向左边声明(直接用符号)函数和数组名
  • * 将名称声明为右边的指针
  • (...) 对于...复杂的情况需要更改默认关联。

分组括号引导您从 inside(标识符)到 outside(左侧的类型说明符,例如“int ").

最后就是指针符号*及其所指的内容。重新制定的(方括号表示可选,此处不是数组!)语法为:

declarator: [* [qual]] direct-declarator
direct-declarator: (declarator)

foo() 是一个 DD(直接声明符)

*foo 是声明符。 (“间接”推导)

*foo() 是 *(foo())。 foo 仍然是一个函数,() 和 [] 绑定最强。 * 是 return 类型。

(*foo)() 使 foo 成为一个指针。一个函数。

顺便说一句,这也解释了为什么在声明符列表中。

int const * a, b

都是const int,但只有a是指针

const 属于 int 而 star 只属于 a。这样就更清楚了,

const int x, *pi

但这已经是边缘混淆了。比如现代诗。适合某些场合。


即使没有括号,解析也会出现轻微的掉头。但这是自然的:

3   2 0  1
int *foo()

这个标准情况(和类似情况)必须很简单。还有著名的 multidim 数组,如 int a[10][10][10].

3    1 0  2
int (*foo)()

此处括号强制“foo”成为左侧(指针)。


复杂的声明在 K&R C 书中有自己的章节。

这是最简单的复杂声明:

int (*(*foo)[])() 

它调试到抽象类型:

int (*(*)[])() 

(*)代替:

int (*F[])() 

缺少数组大小给编译器警告 - “假设一个元素”。

作为抽象类型:

int (*[])()

但是:

int *G[]()

--> 错误:将 'G' 声明为函数数组

是的,你可以,甚至是递归的,但是使用 * 间接 和括号。这样就形成了一个洋葱洋葱,中间是标识符,左边是星星,右边是 [] 和 ()。


C11 规格有这个怪物。 ... 声明可变参数:

int (*fpfi(int (*)(long), int))(int, ...)

删除所有参数后:

int (*fpfi())()

只是一个函数 returning 一个指针。一个函数 returning int.

但是 fpfi 的第一个参数本身就是一个函数 - 一个指向具有 return 类型和它自己的参数的函数的指针:

int (*)(long)

非抽象:

int (*foo)(long)

指向将 long 正式“转换”为 int 的函数的指针。

这是参数。仅有的。 return值也是一个函数指针,指向函数的参数和return类型在最外层。删除整个内部函数 (int (*)(long), int):

int (*pfi)(int, ...)

或更多generic/incomplete:

int (*pfi)()

“T (D)”洋葱规则

所以这个洋葱游戏重演了。 []()* 之间的内向外和右-左-右-左。语法不是问题,语义才是问题。