在声明中,存储类说明符可以放置在哪里?

21
例如,让我们考虑一下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语言标准的相关部分并对其进行解释。


1
为什么 ei 是不合法的?因为它们声明了一个静态指针。指针可以具有静态生命周期。 - Thomas Matthews
1
@ThomasMatthews:这就是我想问的。 :-) Visual C++ 2012、Clang 3.1和g++ 4.7都接受和拒绝相同的声明集合(ei被拒绝;其余被接受)。 - James McNellis
3个回答

18

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

考虑以下情况: 您可以在同一声明列表中声明普通整数和整数指针,如下所示:

int a, *b;

这是因为类型说明符是int,然后您使用该类型说明符int进行了两个声明,a和指针声明符*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

两者均为有效且等效的,因为指针类型被定义为类型说明符,您可以以任何顺序放置类型说明符和存储说明符。请注意,它们都是等效的,并且等效于以下说法

static int *j;
static int *k;
或者
int static *j;
int static *k;

对于这个很好的解释我给一个赞。但是我发现将“存储说明符与类型说明符放在一起,而不是与声明符放在一起”的语法决定真的很不直观。 - cyco130
1
谢谢你,hexist。我突然想到这是因为我养成了两个习惯:我从不在单个声明中声明多个变量,并且我总是将指针星号放在类型旁边。因此,我完全忘记了星号是声明符的一部分。 - James McNellis
@cyco130,就“黄金法则”的背景而言,这是一个简单明了的选择。我已经给出了一个可能会让你恢复直觉的答案。 - oblitum

5
根据C++ 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 的一部分。


非常感谢,安德烈。你是对的,这很明显。正如我刚才提到的,我突然想到,我之所以没有意识到这一点,是因为我养成了两个习惯:我从不在声明中声明多个变量,而且我总是将指针星号放在类型旁边(而不是声明的变量)。我甚至没有考虑过这一点。当我试图混淆规范时,我甚至没有考虑过这一点 :-) - James McNellis
在多重声明中,我们仍然不能忘记可以为单个变量获取constness,尽管无法一次获取多个声明说明符序列。int const a,const b; = 有效 - oblitum

4
如果你采用"黄金法则"(这不仅适用于指针),那么在C/C++中声明变量时,自然、直观且避免了许多错误陷阱。不应违反“黄金法则”(有一些特例,如应用于数组typedef的const,它会将const传播到基本类型,以及C++引用)。
K&R,附录A,第8.4节,声明符的含义。
每个声明符都被视为一种断言:当与声明符相同形式的构造出现在表达式中时,它会产生指定类型和存储类别的对象。
在C/C++中声明变量时,您应该考虑应用于其上以获取基本类型的表达式。
1)必须有一个变量名称
2)然后是作为有效*的表达式,从声明语句中应用于变量名称
3)然后是声明的剩余信息和属性,如基本类型和存储
存储不是您总是可以赋予表达式结果的特征,与const性质相反。它只在声明时才有意义。因此,存储必须出现在不是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希望我们在声明变量时使用倒推的方法,这通常不是一种常见的习惯。当使用时,它可以避免大多数复杂的声明错误和困难。

*valid并不是严格意义上的,因为会出现一些变化,比如x[]、x[size](不是索引)、constness等等...所以2是一个表达式,很好地映射(用于声明使用),"相同的形式",一个反映变量使用,但不是严格的

未经训练者的黄金法则奖励

#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 是一个函数
  • &返回值:其返回值是一个引用
  • 引用[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;

声明约定

因此,我总是把所有不能放在类型前面的内容放在离变量更近的地方(int *a, int &b),而任何可以放在前面的内容,我都放在前面(volatile int c)。

关于这个主题还有更多内容,请访问http://nosubstance.me/post/constant-bikeshedding/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接