缩进#define语句

124
我知道#define等通常不缩进。为什么?
我正在处理一些代码,其中混杂着可怕的#define#ifdef#else#endif等。所有这些都经常与普通的C代码混合在一起。 #define不缩进使它们难以阅读。混合缩进代码和非缩进#define是一场噩梦。
为什么#define通常不缩进?有没有不缩进的原因(例如下面的代码)?
#ifdef SDCC
    #if DEBUGGING == 1
        #if defined (pic18f2480)
            #define FLASH_MEMORY_END 0x3DC0
        #elif defined (pic18f2580)
            #define FLASH_MEMORY_END 0x7DC0
        #else
            #error "Can't set  up flash memory end!"
        #endif
    #else
        #if defined (pic18f2480)
            #define FLASH_MEMORY_END 0x4000
        #elif defined (pic18f2580)
            #define FLASH_MEMORY_END 0x8000
        #else
            #error "Can't set  up flash memory end!"
        #endif
    #endif
#else
    #if DEBUGGING == 1
        #define FLASH_MEMORY_END 0x7DC0
    #else
        #define FLASH_MEMORY_END 0x8000
    #endif
#endif
9个回答

121

早期的 ANSI C 预处理器不允许在行首和 "#" 字符之间有空格;前导的 "#" 必须始终放置在第一列。

现今已经不存在早期的 ANSI C 编译器了。您可以使用任何风格(在 "#" 前加空格或在 "#" 和标识符之间加空格)。

http://www.delorie.com/gnu/docs/gcc/cpp_48.html


32
正如一些人已经说过的那样,一些早期的ANSI编译器要求#符号必须是行首的第一个字符,但它们并不要求预处理指令必须附加在其后,因此缩进就是这样实现的。
#ifdef SDCC
#  if DEBUGGING == 1
#    if defined (pic18f2480)
#      define FLASH_MEMORY_END 0x3DC0
#    elif defined (pic18f2580)
#      define FLASH_MEMORY_END 0x7DC0
#    else
#      error "Can't set  up flash memory end!"
#    endif
#  else
#    if defined (pic18f2480)
#      define FLASH_MEMORY_END 0x4000
#    elif defined (pic18f2580)
#      define FLASH_MEMORY_END 0x8000
#    else
#      error "Can't set  up flash memory end!"
#    endif
#  endif
#else
#  if DEBUGGING == 1
#    define FLASH_MEMORY_END 0x7DC0
#  else
#    define FLASH_MEMORY_END 0x8000
#  endif
#endif

我经常在旧的Unix头文件中看到这种风格,但我讨厌它,因为语法着色经常在这样的代码上失败。我使用非常醒目的颜色来标记预处理指令,以便它们能够突出显示(它们处于元级别,不应该是代码正常流程的一部分)。 你甚至可以看到SO没有以有用的方式对这个序列进行着色。

我经常看到这种指责非传统方式存在的风格,认为是因为无能的特例工具无法处理它们而失败,但我讨厌它,因为人们可能有充分的理由去做一些事情,比如把 # 放在第一个字符位置(例如,即使没有语法高亮,它也可以更好地突出预处理器指令),真正的问题是语法高亮逻辑并不实际匹配它正在突出显示的语法。(明确一点,我仍然+1了这个答案,我只是认为强调这些观点很重要。) - mtraceur

21

关于预处理器指令的解析,C99标准(以及之前的C89标准)清楚地规定了编译器逻辑上执行的操作序列。特别是,我相信它意味着这段代码:

/* */ # /* */ include /* */ <stdio.h> /* */

等同于:

#include <stdio.h>
对于好坏而言,GCC 3.4.4配合'-std=c89 -pedantic'可以接受带有注释的代码行。无论如何我都不会提倡这种风格(它是可怕的),但我认为这是可能的。 ISO/IEC 9899:1999第5.1.1.2节“翻译阶段”说明: 1.字符映射,包括三连符 2.行拼接-删除反斜杠换行符 3.将源文件分解为预处理标记和一系列空格字符(包括注释)。源文件不得以部分预处理标记或部分注释结束。每个注释都替换为一个空格字符。保留换行符。是否保留除换行符之外的每个非空白序列由实现定义。 4.执行预处理指令,展开宏等 第6.10节预处理指令说明: 预处理指令由一系列以#预处理标记开始的预处理标记序列组成,在翻译阶段4开始时,它要么是源文件中的第一个字符(在不包含换行符的空格字符之后)或者跟随至少包含一个换行符的空格字符,并且以下一个换行符结束。 唯一可能引起争议的是括号表达式“(在翻译阶段4开始时)”,这可能意味着哈希前的注释必须不存在,因为它们在直到第4阶段结束之前都没有被替换为空格。 正如其他人所指出的,预标准C预处理器在许多方面的行为不一致,在预处理指令前后的空格是不同编译器执行不同操作的区域之一,包括不识别具有空格的预处理指令。 值得注意的是,反斜杠换行符的删除发生在注释分析之前。因此,你不应该以反斜杠结尾的方式使用//进行注释。

7
我不知道为什么这种做法不太常见。有时候我也喜欢缩进预处理指令。
有一件事情总是让我困扰(有时还会让我放弃尝试),就是很多编辑器/IDE在稍微一点点触动的时候就会把指令扔到第一列,这真的非常烦人。

5
这些天,我认为这主要是一种风格选择。我认为在遥远的过去,不是所有的编译器都支持缩进预处理器定义的概念。我做了一些研究,但无法证实这个说法。但无论如何,现代编译器似乎都支持缩进预处理宏的想法。我没有C或C++标准的副本,因此我不知道这是否是标准行为。
至于是否好的风格。个人而言,我喜欢将它们全部保留在左边。这给你一个一致的地方来查找它们。如果有非常嵌套的宏,可能会很烦人。但如果您缩进它们,最终会得到更奇怪的代码。
#if COND1
void foo() {
  #if COND2
  int i;
    #if COND3
  i = someFunction()
  cout << i << eol;
    #endif
  #endif
}
#endif

19
这段代码看起来很奇怪,因为你创建了两个缩进的“流”。我会将第4行再缩进一层,并将第6行和第7行缩进两个级别。 - Kevin Laity
3
完全同意。 有时我甚至会加上括号,让#if看起来就像是if一样。 - baash05
4
我非常努力地安排我的代码,希望在实际代码部分没有任何 #ifdef 行。相反,如果我需要条件性的东西,我要么将其放在提取出来的函数中,要么放在提取出来的宏中;我发现这样更清晰易懂(至少对我来说是这样)。理想情况下,所有这些提取出来的部分都将位于其他文件中(头文件或有条件编译的源文件;通常“条件”是构建代码的平台)。 - Donal Fellows
2
我会将第4行缩进一级,将第6和7行缩进两级。 - Rocketmagnet

3

在目前可用的大多数C/C++编译器中,排版并没有严格限制。用户可以自由决定代码如何对齐。 祝你编码愉快。


1
不错的回答。您能否加入一些具体的样式指南参考,使其更完善? - EtherDragon

3
对于你提供的示例,使用缩进使其更清晰可能是合适的,因为你有复杂的嵌套指令结构。
个人认为,在大多数情况下最好不要缩进它们,因为这些指令与你的代码分开操作。像 #ifdef 这样的指令由预处理器处理,编译器在看到你的代码之前就会处理它们,所以 #ifdef 指令后面的代码块甚至可能没有被编译。
当指令与代码交织在一起时(而不是像你给出的示例中那样是一个专用的指令块),将指令与代码在视觉上分开更为重要。

3
从IP的角度来看,未编译的东西和由于jmp而未到达的东西有什么区别? - baash05

2
我目前正在处理一些代码,其中包含可怕的混合使用 #define、#ifdef、#else、#endif 等语句。所有这些经常与普通的 C 代码混在一起。#define 的非缩进使其难以阅读。缩进的代码和非缩进的 #define 混合在一起是一个噩梦。
一种常见的解决方案是对指令进行注释,这样您就可以轻松知道它们所指的内容:
#ifdef FOO
/* a lot of code */
#endif /* FOO */

#ifndef FOO
/* a lot of code */
#endif /* not FOO */

6
我见过那种风格,我的老板用的就是。而且,和他其他的代码一样,它只会让事情更加混乱。想象一下,从你平常的if()语句中移除所有缩进,然后改用这些注释来替代。你会抱怨很难看清它们所指向的内容。 - Rocketmagnet

0

我知道这是一个旧话题,但我浪费了几天的时间寻找解决方案。我同意最初的帖子,如果你有很多代码(在我的情况下,我使用指令来启用/禁用详细日志记录),那么意图会使代码更清晰。最后,我找到了这里的解决方案,它适用于Visual Studio 2017

如果您喜欢缩进#pragma表达式,可以在下面启用它:工具>选项>文本编辑器>C/C++>格式化>缩进>预处理器指令的位置>保留缩进

唯一剩下的问题是自动代码布局固定了这种格式=(


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接