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 语言标准的相关部分并进行解释。

4

3 回答 3

18

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

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

int a, *b;

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

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类型被定义为类型说明符,您可以按任何顺序放置类型说明符和存储说明符。请注意,它们都是等价的,相当于说

static int *j;
static int *k;

或者

int static *j;
int static *k;
于 2012-10-25T17:38:16.120 回答
5

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

decl-specifier-seq init-declarator-list;

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

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

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

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


我想说一个更有趣(并且有点相关)的​​情况发生在可以同时出现在单个 decl-specifier-seq声明符中的说明符,比如const说明符。例如,在下面的声明中

int const *a, *b;

是否const适用于所有声明符或仅适用于第一个声明符?语法规定了前一种解释:const适用于所有声明符,即它是decl-specifier-seq.

于 2012-10-25T21:32:01.917 回答
4

如果您采用“黄金法则”(也不仅仅适用于指针),它自然而直观地遵循,并且在 C/C++ 中声明变量时避免了很多错误陷阱。不应违反“黄金法则”(很少有例外,例如const应用于数组 typedef,它传播const到 C++ 附带的基本类型和引用)。

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

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

要在 C/C++ 中声明一个变量,您应该真正考虑应用到它以获取基本类型的表达式。

1)应该有一个变量名

2) 然后表达式 as valid* 从声明语句中出来,应用于变量名

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 希望我们在声明变量时使用反向推理,这通常不是常见的习惯。使用时,它避免了大多数复杂的声明错误和困难。

*valid 不是严格意义上的,因为会发生一些变化,例如 x[]、x[size、not indexing]、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是一个函数
  • &return : 它的返回是一个参考
  • 参考[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 *aint &b),而任何可以放在前面的东西,我都放在(volatile int c)之前。

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

于 2012-10-25T19:45:59.383 回答