Menu

C 语言注释:// 与 /* */ 详解

C 语言有两种注释风格:单行的 // 和多行的 /* */,它们的来历不同,还有一个嵌套陷阱。本文讲解两者的用法,以及哪些内容值得注释、哪些不值得。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

注释是编译器会丢弃的文本。它纯粹是写给以后读代码的人看的,而这个人通常就是你自己。C 语言提供了两种形式,弄清楚什么时候该用哪一种,大约只需要两分钟。

两种形式

运行一下:输出只有一行。两条注释在编译器解析程序之前就被删掉了:它们在运行时没有任何开销,也不会给可执行文件增加任何内容。

// 一直延续到这一物理行的末尾。同一行中它后面不能再写任何代码,所以下面这行的效果和看上去不一样:

int x = 5;  // set x to five  int y = 6;   /* y is never declared */

/* ... */ 在第一个 */ 处结束,无论它在哪里。它可以在行中间开始和结束,偶尔会派上用场:

int total = price /* before tax */ + shipping;

为什么会有两种风格

/* */ 是 1972 年最初的 C 语言就有的。// 来自 C++,直到 C99 才正式加入 C 语言。这段历史解释了你在阅读旧代码时会注意到的一个现象:为了能移植到 C89 而编写的库,连单行注释都用 /* */,因为 // 在它们仍要支持的旧工具链上会编译失败。

如今,你可能用到的每个编译器都同时支持这两种。普通的说明用 //,真正需要跨行的注释用 /* */。如果你的目标是非常老旧的嵌入式编译器,在依赖 // 之前先确认一下。

注释不能嵌套

这是唯一真正的陷阱:

/* Disable this section for now
   int a = compute();
   /* the classic helper - keep an eye on it */
   int b = a * 2;
*/

块注释在第一个 */ 处结束,也就是第 3 行的那个。于是第 4 行和第 5 行又变回了有效代码,而第 6 行末尾的 */ 就成了语法错误。编译器的错误信息会指向最后一行,对真正的原因完全没有帮助。

解决办法是改用预处理器,它能正确处理嵌套:

#if 0
    int a = compute();
    /* the classic helper - keep an eye on it */
    int b = a * 2;
#endif

#if 0 永远不成立,所以预处理器会在编译器看到之前,删除直到 #endif 为止的所有内容。里面有注释、引号或其他 #if 块都不受影响,清理代码时也很容易搜索到。

调试时注释掉代码

临时去掉一行代码,是注释在日常中最常见的用法。程序出现异常时,一次禁用一条语句,就能找出是哪一条在起作用。

取消 printf 的注释,再运行一次,就能看到循环是怎样一步步算出答案的。用打印语句追踪并不优雅,但在 C 语言中它又快又总是管用:调试器能告诉你更多,而 printf 能立刻告诉你一些东西。

有两个习惯可以避免这变成一团糟。提交之前删掉被注释掉的代码;版本控制会记住旧版本,你不需要自己记。如果你有意保留一行被禁用的代码,就在旁边写个说明,解释原因。

文档注释

函数上方的块注释,是用来说明它做什么、参数是什么意思,以及有什么出人意料之处的地方。

Doxygen 之类的工具会读取这样的结构化注释,并生成参考文档。Doxygen 自己的风格使用 /** ... */,配合 @param 和 @return 标签:

/**
 * Converts Celsius to Fahrenheit.
 * @param c temperature in Celsius
 * @return the same temperature in Fahrenheit
 */
double celsius_to_fahrenheit(double c);

在你自己的代码中,哪种都可以。重要的是注释要放在别人会阅读的声明旁边(通常是头文件里),而不是埋在实现代码中。

哪些内容值得注释

在真实代码库中经得起考验的规则是:注释“为什么”,而不是“做了什么”。

i++;  // increment i          <- says nothing the code did not
/* Skip the BOM: files exported by the old system start with
   three bytes that are not part of the data. */
offset += 3;

第二条注释包含了代码中任何地方都没有的信息。第一条则是噪音,迟早会和它所描述的那行代码自相矛盾,因为代码改了,注释往往不会跟着更新。

在 C 语言中,特别值得写注释的内容有:

  • 这块内存归谁所有。 如果函数返回一个调用者必须 free 的指针,就要写明。C 语言没有办法在类型中表达这一点。
  • 单位和范围。 int timeout; 含义不明:是秒还是毫秒?
  • 不那么显而易见的正确性。 为什么循环在 n - 1 处停止,为什么这个类型转换是安全的,为什么缓冲区是 256 字节。
  • 有意为之的古怪写法。 看起来像 bug 但其实不是的代码,如果不加说明,就会招来后来者的“修复”。

这条注释是值得的:它下面那行看起来多余,其实不然。

字符串里的注释不是注释

最后一个细节。在字符串字面量或字符常量中,注释符号没有任何特殊含义:

两行都会完整打印出来。编译器会先识别字符串,再去查找注释,所以引号里的 // 只是两个普通字符。(第一行的 %% 是用 printf 打印一个百分号的方法,单独的 % 会开始一个格式说明符。)

常见问题

C 语言怎么写注释?

有两种写法。// this is a comment 一直延续到行尾。/* this is a comment */ 可以跨越任意多行,在 */ 处结束。两者都会在编译前被删除,所以永远不会影响程序。

C 语言支持 // 注释吗?

支持,从 C99 开始。它借鉴自 C++,如今所有编译器都支持。只有非常古老的 C89 编译器会拒绝它,这就是为什么很老的代码连单行注释都用 /* */。

C 语言的注释可以嵌套吗?

不可以。/* outer /* inner */ still outer */ 在第一个 */ 处就结束了,剩下的 still outer */ 成了错误的代码。要禁用一段本身已包含 /* */ 注释的代码,请改用 #if 0 ... #endif,它可以正确嵌套。

C 语言怎么注释掉一段代码?

如果里面没有块注释,就用 /* */ 包起来,或者在每行前面加 //。对于大段代码,更稳妥的做法是在前面写 #if 0、后面写 #endif:预处理器会删除两者之间的所有内容,里面有注释和引号也不受影响。

Coddy 编程语言插图

和 Coddy 一起学编程

开始