为什么在声明中不能有多个 decl-specifier-seq?

How come you can't have multiple decl-specifier-seq's in a declaration?

本文关键字:decl-specifier-seq 不能 声明 为什么      更新时间:2023-10-16

以下是无效代码:

int i = 0, double j = 2.0;

标准草案说明了原因:

[N4140/7.1.6]

2 作为一般规则,最多允许在 完整的声明说明符序列type-specifier-seq尾随类型说明符-seq。此规则的唯一例外情况如下:

const可以与除自身以外的任何类型说明符结合使用。

volatile可以与除自身以外的任何类型说明符结合使用。

signedunsigned可以与charlongshort、 或int.

shortlong可以与int结合使用。

long可以与double结合使用。

long可以与long结合使用。

是的,它可以防止像int int这样愚蠢的事情,但我看不出上面发布的无效代码有什么问题。引用[N4140/7]一个简单声明由一个 decl-specifier-seq opt init-declarator-listopt;

然后[N4140/8]显示 init-declarator-list 由 init-declarator-list 、 init-declarator、

初始化声明器

声明器初始值设定项选项

由于我们只关心形式 int i = 0 的语法,那么我们关心的声明符ptr-声明符,它是一个 noptr-declarator,它是一个声明符 id 属性说明符-seq opt,最后声明 id 仅由... optid 表达式组成。

为了完整起见,[N4140/5.1.1]id表达式可以是非限定id,也可以只是一个标识符

如果到目前为止我还没有绊倒,这就是语法所反映的。

int decl-specifier-seq

i非限定标识

= 0初始值设定项

int i = 0初始化声明器

由于简单声明具有 decl-specifier-seq,因此只有一个 decl-specifier-seq 适用于整个 init-declarator-list

有趣的是,这意味着你不能做这样的事情:

int i, const j;

还:

int i, * j;

完全合法的,因为星星是PTR运营商的一部分。但你不能这样做:

int i, const * j; // pointer to const int

这意味着在下面的代码中,i成为指向 const int 的指针。 惊喜!

int h = 25;
int const * j, * i = &h;
*i = 50; // error: assignment of read-only location '* i'

意图在[N4140/8]中很明确:

3 声明中的每个初始化声明器分别分析 如果它本身就是在声明中。99

99(具有多个声明符的声明通常等同于 相应的声明序列,每个声明都有一个 说明符。那是

T D1, D2, ... Dn;

通常相当于

T D1; T D2; ... T Dn;

问题是为什么会这样?

<小时 />

如果它是合法的,你可以在循环中做到这一点,这有点用。

简短回答:一个语句只能允许"一个类型的一个声明",但这个声明可以声明"多个标识符"。此外,常量/易失性是类型限定符或指针限定符,因此它们需要绑定到的类型或指针。

长答案:

我从来没有读过标准,但我来了...

"它可以防止像int int这样愚蠢的事情,但我看不出上面发布的无效代码有什么问题。

  • 我的看法是,一个语句只能允许你声明一个声明,但一个声明可以允许你声明相同类型的多个标识符。
  • 所以int i = 0,double
  • j = 2.0的问题在于你有两种类型,int和double。这违反了 [N4140/7.1.6]。
  • 这是语言允许的,因此上述陈述是不正确的。

但是你继续深入挖掘,我相信你的困惑是从这里开始的"因为我们只关心形式的语法......"。宣言不会分手如下吗?

int i = 0 ::= simple-declaration
Where in...
int ::= type-specifier
i ::= identifier
= 0 ::= init-declarator

更多

你提到...

Not Allowed: int i, const j;
Allowed: int i, * j;
Not Allowed: int i, const * j; // pointer to const int
Allowed: int const * j, * i = &h;

我的回应:

Not Allowed: int i, const j; - because const is a type modifier, syntactically there is no type specified to bind to.
Allowed: int i, * j; - because * grabs the type int and j has a complete type.
Not Allowed: int i, const * j; - because const is not associated to a type here. It is the same problem as in the first case. Read it as j is a pointer to <unexpected word in between> and thus screws up the grammar.
Allowed: int const * j, * i = &h; - because syntactically const has a type to bind to.

"问题是为什么会这样?">

  • 当我学习 C 时,我最初对在类型名称之前/之后使用 const 感到困惑,为了消除混淆,我尝试了一些测试代码并弄清楚了语言允许的内容,以下是我想出的。它来自我的旧笔记。它绝对看起来像是新程序员制作的东西。但是,它消除了大部分疑虑。

    [存储类] [符号限定符] [大小限定符] [类型限定符]

    <[*>

    存储类:自动、寄存器、静态、外部、类型定义

    签名

    限定符:已签名、无符号

    尺码限定符:短、长、长

    基本类型:字符,整数,浮点数,双精度,空

    类型限定符:常量、挥发性

    符号名称可以是变量、常量、类型(def(名称和函数

    符号前缀的 * 使其成为指针。

    * 可以出现 N 次,使其成为指针到指针,依此类推。

    符号

    后缀的 [] 使其成为数组。[] 可以出现 N 次,使其成为多维数组。

    符号

    后缀的 (( 使其成为函数。(( 可以出现 N 次,但由于函数无法返回函数,因此当函数返回函数指针时,(( 可以再次出现。

以上内容帮助我在声明变量时直接思考。

修改我古老的笔记中的类型说明符语法:

[storage class] [sign qualifier] [size qualifier] <type> [type qualifiers] [* [pointer qualifiers]] [symbol name] [[] | ([parameters])]

也就是说,常量和易失性要么是类型限定符,要么是指针限定符,它们需要类型或指针来绑定才能限定它们。

考虑"一个语句只能允许你声明一个声明,但一个声明可以允许你声明相同类型的多个标识符"的想法。这意味着类型说明符语法可以细分如下:

type ::= [storage class] [sign qualifier] [size qualifier] <type> [type qualifiers]
symbol ::= [* [pointer qualifiers]] [symbol name] [[] | ([parameters])]

声明的简化语法为:

类型符号[, 符号...]

清楚

int i, const j; - 不同意语法。

int const i, j; - 同意语法。

我相信一个精通标准的人可以使用该标准并使用正确的术语和参考资料提供答案。但是,请记住,经验不足的程序员可能会发现技术性较低的答案很容易理解。

如果允许使用"int i, const j"的形式,那么可以写成"int const i, const j",这意味着j是一个双精度常数。这没有任何意义。

问题是为什么会这样?

不幸的是,我不能肯定地回答这个问题。我的猜测是,这是一个速记,用于在 C 或其前身之一中保存击键。我会说,恕我直言,*运算符确实更改了您声明的变量的类型,因此我不知道为什么在不允许的情况下允许const变量。

我还想添加一个您尚未包含的示例,这是合法的

int i = 0, * const j = &i;

这是合法的,因为在这种情况下,const适用于*而不是int。至少根据 ideone。

这很可能只是需要发扬光大的好处。

您从标准中提取的内容阐明了该主题:

99) A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator. That is
T D1, D2, ... Dn;
is usually equivalent to
T D1; T D2; ... T Dn;

意图似乎是当用逗号分隔在一起声明时,Dn将是相同的类型。因为如果您要更改类型,则不妨使用分号,因为这不会为您节省击键,即

int i, j = 0; double k, l = 7.3;

在这里,通过在正确的位置使用逗号,您可以节省自己键入intdouble两次的时间。

我可以用constvolatile等类型修饰符看到你的观点。为什么不让我们随意合并呢?我的意思是这与*有何不同?我不认为这有什么不同,我希望比我聪明的人会来解释原因。我会说要么禁止*,要么也允许我们使用其他修饰符。

简而言之,我不知道,但这里有一个很酷的疯狂例子

int i = 0, * const j = &i;

问题是为什么会这样?

因为这就是《时间黎明时的C》(实际上是1973

年(中的样子

如果它是合法的,你可以在循环中做到这一点,这有点用。

这是合法的,只是不是很漂亮:

for(std::tuple<int, double> t = std::make_tuple(0, 10.0) ; 
    std::get<0>(t) < 10 ; 
    ++std::get<0>(t), std::get<1>(t) *= 1.1)
{
    cout << std::get<0>(t) << ", " << std::get<1>(t) << std::endl;
}