声明中的哪个位置可以放置存储类说明符?

问题描述 投票:19回答:3

例如,让我们考虑static存储类说明符。以下是此存储类说明符的有效和不正确用法的几个示例:

static int a;        // valid
int static b;        // valid

static int* c;       // valid
int static* d;       // valid
int* static e;       // ill-formed

static int const* f; // valid
int static const* g; // valid
int const static* h; // valid
int const* static i; // ill-formed

typedef int* pointer;
static pointer j;    // valid
pointer static k;    // valid

(标记为“有效”的声明被Visual C ++ 2012,g ++ 4.7.2和Clang ++ 3.1接受。标记为“格式错误”的声明被所有这些编译器拒绝。)

这看起来很奇怪,因为存储类说明符适用于声明的变量。它是声明的变量static,而不是声明的变量的类型。为什么ei形成不良,但k结构良好?

有效放置存储类说明符的规则是什么?虽然我在这个例子中使用了static,但问题适用于所有存储类说明符。优选地,完整的答案应引用C ++ 11语言标准的相关部分并解释它们。

c++ declaration storage-class-specifier
3个回答
17
投票

总之,声明说明符中的任何位置(参见ISO / IEC 14882-2012中的第7.1节),即在*之前。 *之后的限定符与指针声明符相关联,而不是类型说明符,而static在指针声明符的上下文中没有意义。

请考虑以下情况:您可以在同一个声明列表中声明一个普通的int和一个指向int的指针,如下所示:

int a, *b;

这是因为类型说明符是int,那么你有两个声明使用类型说明符inta和一个指针声明符*a,它声明了一个指向int的指针。现在考虑:

int a, static b;  // error
int a, *static b; // error
int a, static *b; // error

它应该看起来是错误的(因为它们是),原因(如第7.1和8.1节所定义)是因为C和C ++要求您的存储说明符与您的类型说明符一致,而不是在您的声明符中。所以现在应该清楚的是,以下也是错误的,因为上述三个也是错误的:

int *static a; // error

你最后一个例子,

typedef int* pointer;
static pointer j;    // valid
pointer static k;    // valid

两者都是有效的并且都是等价的,因为pointer类型被定义为类型说明符,您可以按任何顺序放置类型说明符和存储specifeir。请注意,它们都是等价的,相当于说

static int *j;
static int *k;

要么

int static *j;
int static *k;

5
投票

根据7.1,C ++声明的[简化]结构是

decl-specifier-seq init-declarator-list;

根据7.1 / 1,存储类说明符属于最初的“公共”部分decl-specifier-seq

根据8/1,init-declarator-list是一系列声明者。

根据8/4,指针声明的*部分是该序列中单个声明符的一部分。这立即意味着*之后的所有内容都是该单个声明者的一部分。这就是您的某些存储类说明符展示位置无效的原因。声明符语法不允许包含存储类说明符。

基本原理是相当明显的:由于存储类说明符应该适用于整个声明中的所有声明符,因此它们被放入声明的“公共”部分。


我会说,一个更有趣(并且有些相关)的情况发生在可以出现在decl-specifier-seq和单个声明符中的说明符,例如const说明符。例如,在以下声明中

int const *a, *b;

const适用于所有声明者还是仅适用于第一个声明者?语法决定了前者的解释:const适用于所有声明者,即它是decl-specifier-seq的一部分。


3
投票

如果你使用"Golden Rule"(它也不仅仅适用于指针),它自然而直观地遵循,并且在C / C ++中声明变量时避免使用a lot of mistakespitfalls。不应该违反“黄金法则”(有很少的例外,例如const应用于数组typedef,它将const传播到基类型,以及引用,与C ++一起提供)。

K&R,附录A,第8.4节,声明者的含义:

每个声明符都被认为是一个断言,当一个与声明符相同的形式的结构出现在表达式中时,它产生一个指定类型和存储类的对象。

要在C / C ++中声明变量,您应该考虑应该应用于它的表达式来获取基类型。

1)应该有一个变量名

2)然后将表达式作为声明语句中的有效*,应用于变量名称

3)然后是基本类型和存储的声明的剩余信息和属性

存储不是一个你可以总是赋予表达式结果的特性,例如与constness相反。只有在声明时它才有意义。所以存储必须来自其他不在2的地方。

int * const *pp;
/*valid*/

int * static *pp;
/*invalid, this clearly shows how storage makes no sense for 2 and so breaks   */
/*the golden rule.                                                             */
/*It's not a piece of information that goes well in the middle of a expression.*/
/*Neither it's a constraint the way const is, it just tells the storage of     */
/*what's being declared.                                                       */

我认为K&R希望我们在声明变量时使用反向推理,这通常不是常见习惯。使用时,它避免了大多数复杂的声明错误和困难。

*有效并不是严格意义上的,因为会出现一些变化,例如x [],x [size,not indexing],constness等...所以2是一个映射得很好的表达式(用于声明用法),“相同形式“,一个reflects variable's use,但not strictly

Golden Rule Bonus for the Uninitiated

#include <iostream>

int (&f())[3] {
    static int m[3] = {1, 2, 3};
    return m;
}

int main() {
    for(int i = 0; i < sizeof(f()) / sizeof(f()[0]); ++i)
        std::cout << f()[i] << std::endl;

    return 0;
}

在声明的上下文中,&不是获取地址的操作,它只是告诉什么是引用。

  • f()f是一个功能
  • &return:它的回报是一个参考
  • reference[3]:引用是一个包含3个元素的数组
  • int array [i]:一个元素是一个int

所以你有一个函数返回对3个整数数组的引用,并且因为我们有正确的数组大小的编译时间信息,我们可以随时用sizeof检查它=)

最后的黄金提示,对于任何可以放在类型之前的东西,当在多个声明中时,它将立即应用于所有变量,因此不能单独应用。

这个const不能放在int之前:

int * const p;

所以以下是有效的:

int * const p1, * const p2;

这个可以:

int const *p; // or const int *p;

所以以下内容无效:

int const *p1, const *p2;

可交换的const适用于所有人:

int const *p1, *p2; // or const int *p1, *p2;

Declaration Conventions

因此,我总是把所有不能放在类型之前的东西,更接近变量(int *aint &b),以及之前可以放的任何东西,我放在之前(volatile int c)。

http://nosubstance.me/post/constant-bikeshedding/上有关于这个主题的更多内容。

© www.soinside.com 2019 - 2024. All rights reserved.