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 是一种类型(例如 int
或 double
或 struct foo
或任何 typedef 名称),并且D1 是声明符。
声明符可能非常复杂,但是由少量的简单步骤构成的。他们提到它是归纳定义的,这是说它具有递归定义的另一种方式。
最简单的声明符就是一个标识符名称,例如x
或foo
。所以 T 可能是 int
而 D1 可能是 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)
中,ident 是 foo
.
第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.:
- C 不允许函数数组和函数返回函数或数组。这些在语法中都可以很好地表达,但 C 的语义检查会希望你这样做,但那里有一个 pointer-to link 来防止这些。
如果你想玩这些 - cdecl.org 可能会很有帮助,甚至可能更有帮助,因为它不进行所说的语义检查,因此甚至可以让你像
int foo[]()();
(数组函数返回函数返回 int),这很好地演示了语法的工作原理,即使 C 编译器会拒绝它们。
...binding of complicated declarators
这是规范中非常好的提示。
规则本身真的很难分析,因为它是递归的。 declaration 和 declarator 的关系和部分也是相关的。
结果是:
()
和[]
是最里面的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)”洋葱规则
所以这个洋葱游戏重演了。 []
、()
和 *
之间的内向外和右-左-右-左。语法不是问题,语义才是问题。
我正在阅读 C99 ISO 标准中的声明符,但我很难理解以下段落:
5 If, in the declaration ”
T D1
”,D1
has the formidentifier
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 是一种类型(例如 int
或 double
或 struct foo
或任何 typedef 名称),并且D1 是声明符。
声明符可能非常复杂,但是由少量的简单步骤构成的。他们提到它是归纳定义的,这是说它具有递归定义的另一种方式。
最简单的声明符就是一个标识符名称,例如x
或foo
。所以 T 可能是 int
而 D1 可能是 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 asint
) andD1
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)
中,ident 是 foo
.
第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.:
- C 不允许函数数组和函数返回函数或数组。这些在语法中都可以很好地表达,但 C 的语义检查会希望你这样做,但那里有一个 pointer-to link 来防止这些。 如果你想玩这些
- cdecl.org 可能会很有帮助,甚至可能更有帮助,因为它不进行所说的语义检查,因此甚至可以让你像
int foo[]()();
(数组函数返回函数返回 int),这很好地演示了语法的工作原理,即使 C 编译器会拒绝它们。
...binding of complicated declarators
这是规范中非常好的提示。
规则本身真的很难分析,因为它是递归的。 declaration 和 declarator 的关系和部分也是相关的。
结果是:
()
和[]
是最里面的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)”洋葱规则
所以这个洋葱游戏重演了。 []
、()
和 *
之间的内向外和右-左-右-左。语法不是问题,语义才是问题。