【问题标题】:What makes a better constant in C, a macro or an enum?什么使 C、宏或枚举中的常量更好?
【发布时间】:2013-06-12 02:34:43
【问题描述】:

我对何时使用宏或枚举感到困惑。两者都可以用作常量,但是它们之间有什么区别,两者的优点是什么?它是否与编译器级别有关?

【问题讨论】:

  • 当您这样考虑时,我认为这是一个很好的问题:在 C 中使用宏和枚举定义常量有什么区别?

标签: c macros enums


【解决方案1】:

宏是一种预处理器,编译后的代码不知道您创建的标识符。在代码到达编译器之前,它们已经被预处理器替换。枚举是一个编译时实体,编译后的代码保留了有关符号的完整信息,这些信息在调试器(和其他工具)中可用。

首选枚举(如果可以的话)。

【讨论】:

  • 我认为在调试的情况下枚举比宏更舒服。
  • 不是争论,而是使用gcc -g3,你可以编译一个gdb友好的可执行文件,保留宏的信息。
【解决方案2】:

在可读性方面,枚举比宏更适合常量,因为相关的值被组合在一起。此外,enum 定义了一个新类型,因此程序的读者可以更轻松地找出可以传递给相应参数的内容。

比较

#define UNKNOWN  0
#define SUNDAY   1
#define MONDAY   2
#define TUESDAY  3
...
#define SATURDAY 7

typedef enum {
    UNKNOWN,
    SUNDAY,
    MONDAY,
    TUESDAY,
    ...
    SATURDAY,
} Weekday;

这样的代码更容易阅读

void calendar_set_weekday(Weekday wd);

比这个

void calendar_set_weekday(int wd);

因为你知道可以传递哪些常量。

【讨论】:

  • 确实,但同样可以坚持使用宏,然后 typedef int Weekday; 因为 typedef 和枚举在 C 中都不是类型安全的(在这个意义上)...
  • @H2CO3:在gdb,我使用p (int)Thursday,或p (Weekday)3
  • @VarunChhangani 喜欢enum foo { BAR = 3, BAZ = 1 << 9 }?是的
  • 顺便说一句,在枚举列表的最后一项后面加逗号是合法的,因此不需要奇怪的逗号格式。
  • @jamesdlin 我最近发现它在 ANSI C 中是合法的。这让我很惊讶(我一直认为主要优点是它使代码生成更容易一些)但是你有它。
【解决方案3】:

实际上,差别不大。它们同样可以用作程序中的常量。有些人可能出于风格原因更喜欢其中一种,但我想不出任何技术上的理由来偏爱其中一种。

一个区别是宏允许您控制相关常量的整数类型。但是enum 将使用int

#define X 100L
enum { Y = 100L };

printf("%ld\n", X);
printf("%d\n", Y); /* Y has int type */

【讨论】:

  • 在 C 中,enum 常量总是int,对于更宽的类型没有这样的规则。您可能正在与 C++ 混合使用?
  • @JensGustedt:啊,被另一个 GCC 扩展捕获了。已修复,谢谢。
【解决方案4】:

请注意,宏和枚举之间存在一些差异,这些属性中的任何一个都可能使它们(不)适合作为特定常量。

  • 枚举已签名(与 int 兼容)。在任何需要无符号类型的上下文中(尤其是按位运算!),枚举都不存在了。
  • 如果 long long 比 int 宽,大常量将不适合枚举。
  • 枚举的大小(通常)是sizeof(int)。对于小值数组(例如 CHAR_MAX),您可能需要 char foo[] 而不是 enum foo[] 数组。
  • 枚举是整数。你不能有enum funny_number { PI=3.14, E=2.71 }
  • 枚举是 C89 功能; K&R 编译器(诚然是古老的)不理解它们。

【讨论】:

  • 实际上,枚举的大小是not guaranteedint 相同,尽管出于二进制兼容性的原因,实际上大多数编译器都会使用int
  • +1。人们经常使用枚举来声明常量。
【解决方案5】:

如果宏被正确实现(即它在被替换时不会出现关联性问题),那么在 两者 都适用的情况下,宏和枚举常量之间的适用性没有太大差异,即在情况下您需要专门的有符号整数常量。

但是,一般情况下,宏提供了更灵活的功能。枚举将特定类型强加到您的常量上:它们将具有类型 int(或者,可能是更大的有符号整数类型),并且它们将始终是有符号的。使用宏,您可以使用常量语法、后缀和/或显式类型转换来生成任何类型的常量。

当您有一组紧密关联的连续整数常量时,枚举的效果最好。当您根本不关心常量的实际值时,它们特别有效,即当您只关心它们具有 一些 行为良好的唯一值时。在所有其他情况下,宏是更好的选择(或者基本上是唯一的选择)。

【讨论】:

    【解决方案6】:

    在 C 中,最好使用枚举进行实际枚举:当某个变量可以保存多个可以命名的值之一时。枚举的一个优点是编译器可以执行一些超出语言要求的检查,例如枚举类型的 switch 语句不会丢失任何一种情况。枚举标识符也传播到调试信息中。在调试器中,您可以将标识符名称视为枚举变量的值,而不仅仅是数值。

    枚举只能用于创建整数类型的符号常量的副作用。例如:

    enum { buffer_size = 4096 };  /* we don't care about the type */
    

    这种做法并没有广泛传播。一方面,buffer_size 将用作整数而不是枚举类型。调试器不会将4096 呈现为buffer_size,因为该值不会表示为枚举类型。如果您声明了一些char array[max_buffer_size];,那么sizeof array 将不会显示为buffer_size。在这种情况下,枚举常量在编译时就消失了,所以它也可能是一个宏。并且有一些缺点,比如无法控制它的确切类型。 (在某些情况下,翻译预处理阶段的输出被捕获为文本可能会有一些小的优势。宏将变成 4096,而buffer_size 将保持为buffer_size

    预处理器符号让我们可以这样做:

    #define buffer_size 0L /* buffer_size is a long int */
    

    请注意,来自 C 的 <limits.h> 中的各种值(例如 UINT_MAX)是预处理器符号而不是枚举符号,这是有充分理由的,因为这些标识符需要具有精确确定的类型。预处理器符号的另一个优点是我们可以测试它的存在,甚至可以根据它的值做出决定:

    #if ULONG_MAX > UINT_MAX 
    /* unsigned long is wider than unsigned int */
    #endif
    

    当然,我们也可以测试枚举常量,但不能根据结果更改全局声明。

    枚举也不适合位掩码:

    enum modem_control { mc_dsr = 0x1, mc_dtr = 0x2, mc_rts = 0x4, ... }
    

    这只是没有意义,因为当这些值与按位 OR 组合时,它们会产生一个超出类型的值。如果将这样的代码移植到 C++ 中,它也会让人头疼,因为 C++ 有(更多的)类型安全枚举。

    【讨论】:

    • 我们能不能在编译时改变枚举的值,const等于枚举或宏
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-07
    • 1970-01-01
    • 1970-01-01
    • 2016-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多