注解标签(attributes)

📅 2026/7/2 11:24:47
注解标签(attributes)
本小节介绍的属性标签attributes在其他语言中又叫注解annotations又叫在 C98/03 时代不同的编译器使用不同的注解去为代码增加一些额外的说明读者可能在各种 C/C 代码中见过像#pragma、__declspec、__attribute等注解。然而不同的编译器对于同一功能可能使用不同的注解这样导致我们需要为不同的编译器编写针对那个编译器的注解代码。从 C11 开始新的语言标准开始统一制定了一些常用的注解标签本节我们来介绍一些比较常用的。使用注解标签的语法是[[attribute]] types/functions/enums/etc这些标签可用于修饰任意类型、函数或者 enumeration在 C17 之前不能用于修饰命名空间namespace和 enumerator在 C17 标准中这个限制也被取消了。读者可能对 enumeration 和 enumerator 这两个词感到困惑前者指的从 C 时代就存在的不限定作用域的枚举。例如下面的枚举类型就是一个 enumeration//一个 enumeration 的例子 enum Color { black, white, red }; //无法编译通过 bool white true;这种枚举类型之所以称之为不限定作用域的枚举是因为一旦定义了这样一种枚举在其所在的作用域内不能再定义与之同名的变量了。例如如果定义了上述 Color 枚举此时再定义一个名叫 white 的变量是无法编译通过的。而enumerator指的是从 C11 开始引入的、如下形式定义的枚举变量//一个 enumerator 的例子 enum class Color { black, white, red }; //可以编译通过 bool white true;此时由于枚举值 white 对外部不可见必须通过 Color::white 来引用可以定义一个同名的 white 变量。这种枚举变量被称之为限定作用域的枚举。在分清楚了enumeration 和 enumerator 之后让我们回到正题上来。C11 引入了的常用的注解标签有[[noreturn]]这个注解的含义是告诉编译器某个函数没有返回值例如[[noreturn]] void terminate();这个标签一般在设计一些系统函数时使用例如std::abort()和std::exit()。C14 引入了[[deprecated]]标签用于表示一个函数或者类型等已经被弃用当你使用这些被弃用的函数或者类型编译时编译器会给出相应的警告有的编译器直接产生编译错误。[[deprecated]] void funcX();这个标签在实际开发中非常有用尤其在设计一些库代码时如果库的作者希望某个函数或者类型不想再被用户使用可以使用该标注标记。当然你也可以使用以下语法给出编译时的具体警告或者出错信息[[deprecated(use funY instead)]] void funcX();有如下代码#include iostream [[deprecated(use funcY instead)]] void funcX() { //实现省略... } int main() { funcX(); return 0; }我在 main 函数中调用被标记为 deprecated 的函数 funcX在 gcc/g 7.3 中编译会得到如下警告信息[rootmyaliyun testmybook]# g -g -o test_attributes test_attributes.cpp test_attributes.cpp: In function ‘int main()’: test_attributes.cpp:10:11: warning: ‘void funcX()’ is deprecated: use funcY instead [-Wdeprecated-declarations] funcX(); ^ test_attributes.cpp:3:42: note: declared here [[deprecated(use funcY instead)]] void funcX() ^~~~~对于 Java 开发者来说这个标注应该是再熟悉不过了Java 中使用 Deprecated这大概是 C 标准委员会拖欠广大 C 开发者太久的一个特性吧好在时至今日我们终于等到了。C 17 提供了如下三个实用注解[[fallthrough]][[nodiscard]][[maybe_unused]]让我们逐一来介绍它们的用法。[[fallthrough]]用于 switch-case 语句中当某个 case 分支执行完毕后如果没有 break 语句编译器可能会给出一条警告但有时候这可能是开发者故意为之的为了让编译器明确的知道开发者的意图可以在需要某个 case 分支被“贯穿”处上一个 case 没有 break显式设置[[fallthrough]]标记。代码示例如下switch (type) { case 1: func1(); //这个位置缺少break语句且没有fallthrough标注 //可能是一个逻辑错误编译时编译器可能会给出警告以提醒修改之 case 2: func2(); //这里也缺少break语法但是使用了fallthrough标注 //说明是开发者有意为之编译器不会给出任何警告 [[fallthrough]]; case 3: func3(); }注意在 gcc/g 中[[fallthrough]]后面的分号不是必须的在 Visual Studio 中必须加上分号否则无法编译通过。熟悉 go 语言的读者可能对 fallthrough 这一语法特性非常熟悉go 中在 switch-case 后加上 fallthrough 是一个常用的告诉编译器意图的语法规则。例如//以下是 go 语法 s : abcd switch s[3] { case a: fmt.Println(The integer was 4) fallthrough case b: fmt.Println(The integer was 5) fallthrough case c: fmt.Println(The integer was 6) default: fmt.Println(default case) }[[nodiscard]]一般用于修饰函数告诉函数调用者必须关注该函数的返回值即不能丢弃该函数返回值。如果调用者未将该函数的返回值赋值给一个变量编译器会给出一个警告。例如假设有一个网络连接函数 connect我们通过返回值明确的说明了连接是否建立成功为了防止调用者在使用时直接将其值丢弃我们可以将该函数使用[[nodiscard]]标注标记[[nodiscard]] int connect(const char* address, short port) { //实现省略... } int main() { connect(127.0.0.1, 8888); return 0; }到 C20 中对于诸如 operator new()、std::allocate() 等库函数均使用了[[nodiscard]]进行了标记以强调必须使用这些函数的返回值。[[maybe_unused]]有些编译器会对程序代码中未被使用的函数或变量给出警告在 C17 之前程序员们为了消除这些警告要么修改编译器警告选项设置要么定义一个类似于 UNREFERENCED_PARAMETER 的宏来显式调用这些未使用的变量一次以消除编译警告。#define UNREFERENCED_PARAMETER(x) x int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); //...无关代码省略 }上述代码节选自一个标准的 Win32 程序的结构其中函数参数 hPrevInstance 和 lpCmdLine 一般不会被用到编译器会给出警告为了消除这类警告定义了一个宏 UNREFERENCED_PARAMETER并调用之造成这两个参数被使用到的假象。有了[[maybe_unused]]注解之后我们再也不需要这类宏来“欺骗”编译器了。上述代码使用该注解可以修改如下int APIENTRY wWinMain(HINSTANCE hInstance, [[maybe_unused]] HINSTANCE hPrevInstance, [[maybe_unused]] LPWSTR lpCmdLine, int nCmdShow) { //...无关代码省略 }读者可以通过 Attribute specifier sequence (since C11) - cppreference.com 页面了解 C 新标准中更多的注解的用法。