6.7.1 一般规定
IMPORTANT
这一小节先规定“声明”在语法上长什么样,再说明什么情形算定义、什么时候允许只有说明符而没有声明符列表,以及属性附着到哪里。
语法
声明可以是以下几类:
- 普通声明:
declaration-specifiers init-declarator-listopt ; - 以前置属性开头的声明
static_assert声明- 单独的属性声明
- 普通声明:
普通声明中的核心部件有三层:
declaration-specifiers说明存储类、类型、函数说明符等;init-declarator-list列出一个或多个声明符;initializer可选,用于给对象提供初始值。
约束
如果某个声明既不是
static_assert声明,也不是属性声明,并且它没有init-declarator-list,那么它的声明说明符中必须至少包含以下两类之一:- 带标记的
struct、union或enum说明符,并且该声明属于6.7.3.4规定的“声明该标记”的形式; - 带枚举项列表的
enum说明符。
- 带标记的
下面这类写法无效,因为被声明的标记或枚举项藏在更深一层构造里,不算“当前声明本身在声明它”:
struct { struct s2 { int x2a; } x2b; };
typeof(struct s3 { int x3; });
alignas(struct s4 { int x4; }) int;
typeof(struct s5 *);
typeof(enum { E6 });
struct { void (*p)(struct s7 *); };2
3
4
5
6
如果某个标识符没有链接,那么在同一作用域、同一命名空间里,不能出现多个对它的声明;但有两个例外:
typedef名可以重新声明为它当前已经表示的同一类型,只要该类型不是变长修改类型;- 枚举常量和标记可以按
6.7.3.3、6.7.3.4的规则重声明。
同一作用域中若多个声明指向同一个对象或函数,它们必须指定兼容类型。
语义
声明用于规定一组标识符的解释方式和属性。
对某个标识符而言,下面这些声明属于它的“定义”:
- 对象:该声明为对象保留存储;
- 函数:该声明带有函数体;
- 枚举常量:第一次声明;
typedef名:第一次声明。
声明说明符序列负责给出:
- 链接;
- 存储期;
- 类型的一部分;
- 以及可能附着到类型上的属性。
init-declarator-list是逗号分隔的一组声明符;每个声明符都可以再附带自己的类型派生信息、初始化器,或者二者兼有。如果声明尾部有一段属性说明符序列,那么这段属性附着到
init-declarator-list中声明出来的每个实体上。如果某个无链接对象被声明,那么它的类型必须在以下时点之一已经完整:
- 没有初始化器时,在其声明符结束前;
- 有初始化器时,在其
init-declarator结束前。
对函数形参来说,这里要求完整的是调整后的类型,也就是
6.7.7.4中把数组调成指针、把函数调成函数指针之后的类型。如果一串声明说明符后面紧接着还有属性说明符序列,那么这段属性附着到前面那串说明符所决定的类型上;它只影响当前这条声明,不会自动影响同一类型的其他声明。
除标准另有规定外,单独的属性声明有什么语义,由实现定义。
欠说明声明
如果一个声明:
- 声明说明符里没有类型说明符;或者
- 使用了
constexpr;
那么它被称为欠说明声明。
对这类声明,如果出现下列任一情况,其行为由实现定义:
- 它不是定义;
- 它没有声明普通标识符,或者声明了多个普通标识符;
- 被声明标识符在同一作用域中已经有声明;
- 被声明实体不是对象;
- 这条声明内部又声明了不是普通标识符的名字。
例如:
constexpr typeof(struct s *) x = 0;这里内部同时声明了结构体标记 s,而标记不属于普通标识符,因此整体行为由实现定义。
一个容易忽略的属性位置
实体属性既可以写在声明开头,也可以写在该实体标识符之后:
[[deprecated]] void f [[deprecated]] (void);这在标准中是有效的。