当前位置: C语言 -- 基础 -- 声明

声明(十九)

十二、属性

属性(attributes)是ISO/IEC 9899:2024标准新增内容。属性为各种源构造(例如:类型、对象、标识符或者块。)指定额外信息。属性通过属性标记进行标识,属性标记可以是带前缀的属性标记(此类标记用于实现指定的属性,例如:gnu::aligned(16)。)或者由标识符指定的标准属性(用于指定ISO标准指定的属性,例如:deprecated。)。

根据ISO/IEC 9899:2024标准,属性是可选特性,实现可以支持,也可以不支持,甚至可以部分支持。对于ISO标准未指定的属性(包括带前缀的属性标记),其行为由实现定义。任何实现不支持的属性标记将被忽略。


属性认为属于某个源构造,其所属关系由属性出现的语法上下文确定。对于每个属性,对应的子句会限制该属性所属关系的有效语法上下文。属于某个源构造的属性指定序列只能包含允许用于该源构造的属性。

标识符attr和标识符__attr__除拼写格式不同外,在作为属性标记使用时应具有相同的行为,例如:属性[[noreturn]]和属性[[__noreturn__]]可以互换。实现应确保提供的属性标记(包括带前缀的属性标记)具有类似行为。

[[noreturn]] int func(void);  //等价于[[__noreturn__]] int func(void);。 

对于ISO/IEC 9899:2024标准指定的所有标准属性,当其标记序列传递给__has_c_attribute条件包含表达式时,其值应为202311L,例如:__has_c_attribute( nodiscard )值为202311L


属性说明符(attribute-specifier)具有以下语法格式:

[[ attribute-list ]]

属性列表(attribute-list)中可以存在0个、1个或者多个属性;如果存在多个属性,各属性间使用逗号(,)分隔,属性具有以下语法格式:

attribute-token arrtibute-argument-clauseopt

属性标记(attribute-token)可以是标准属性(standard-attribute),也可以是带前缀的属性标记(attribute-prefixed-token);属性实参子句(arrtibute-argument-clause)是可选的。

标准属性是标识符,ISO/IEC 9899:2024标准支持的标准属性如下表所示:

标准属性中的标识符
deprecated maybe_unused noreturn unsequenced
fallthrough nodiscard _Noreturn reproducible

带前缀的属性标记具有以下语法格式:

identifier :: identifier

第一个标识符(identifier)是属性前缀(attribute-prefix),例如:gnu

属性实参子句具有以下语法格式:

( balanced-token-sequenceopt )

平衡标记序列(balanced-token-sequence)是可选的,平衡标记(balanced-token)中括号应严格配对,例如:(符号必须存在对应的)符号。平衡标记应具有以下任一语法格式:

( balanced-token-sequenceopt )
[ balanced-token-sequenceopt ]
{ balanced-token-sequenceopt }
除(、)、[、]、{、}外的任何标记

不包含属性的属性说明符没有任何作用。如果属性标记中包含满足标识符语法要求的关键词,则该关键词视为标识符。使用标准属性严格符合ISO标准的程序在缺少标准属性时仍严格符合ISO标准。如果ISO标准规定的标准属性被实现忽略,不会改变程序语义;对于ISO标准未规定的属性,情况并非如此。

对于标准属性(如果存在),其平衡标记序列的格式是指定的。实现应为带前缀的属性标记定义一个唯一的属性前缀名,例如:gnu。实现不应定义没有属性前缀的属性,除非该属性是ISO标准规定的标准属性。


同一属性列表中属性标记的出现顺序无关紧要。

假设AP是实现支持的属性前缀名,MN分别是特定的属性名。

//等价于[[AP::N, AP::M]] int f1(int);。
//因为同一属性列表内属性标记的先后顺序无关紧要。
[[AP::M, AP::N]] int f1(int);         

//不一定等价于[[AP::N]] [[AP::M]] int f2(int);。
//属性说明符的先后顺序不同可能影响语义。
[[AP::M]] [[AP::N]] int f2(int);    

1、nodiscard属性

nodiscard属性用于强制编译器检查函数返回值或者类型是否正确处理,从而提高代码健壮性,避免潜在错误。nodiscard属性说明符具有以下两种语法格式:

[[nodiscard]]
[[nodiscard( string-literal )]]

其中nodiscard是标准属性标识符,string-literal是字符串字面量。


如果实现支持ISO/IEC 9899:2024标准,nodiscard用作__has_c_attribute表达式操作数时,表达式值为202311L,即__has_c_attribute( nodiscard )值为202311L


nodiscard属性应用于结构类型、联合类型、枚举类型的定义或者函数。

[[nodiscard("Need to check the state.")]] bool func(int);

struct [[nodiscard]] s{
  int i;
  double d;
};

如果名称或者实体声明时未使用[[nodiscard]]属性说明符,后续声明可以使用该属性说明符重新声明,反之亦然。实体从首次使用[[nodiscard]]属性说明符声明开始,后续所有使用均受此属性约束。

nodiscard调用是一种函数调用表达式,该表达式调用了一个声明时具有nodiscard属性的函数,或者函数返回类型具有nodiscard属性的结构、联合或者枚举类型。

[[nodiscard("Need to check the state.")]] bool f1(int);

struct [[nodiscard]] s{
  int i;
  double d;
};
struct s f2(struct s *);
...
struct s s1 = {3, 3.14};

int i = f1(0);		//nodiscard调用。
struct s s2 = f2(&s1);	//nodiscard调用。

ISO标准不建议将nodiscard调用作为void表达式求值,除非显式转换为void类型表达式。实现应在这种情况下发出诊断信息,因为立即丢弃nodiscard调用的返回值通常会产生意想不到的后果。诊断信息应包含属性实参子句中的字符串字面量文本。

f1(0);		//实现应发出诊断信息。
(void)f1(0);	//实现不需要发出诊断信息。
int i = f1(0);	//即使i未使用,实现也不需要发出诊断信息。