【问题标题】:Advantage and disadvantages of #define vs. constants? [duplicate]#define 与常量的优缺点? [复制]
【发布时间】:2010-12-28 23:30:17
【问题描述】:

有人能指出使用#define 与常量相比的优缺点吗?我的大部分工作都是用 C 和 Objective-C 完成的。

【问题讨论】:

  • #define's 不尊重范围。

标签: c objective-c


【解决方案1】:

As 0A0D mentioned,有#definesenumsconst变量。值得注意的是,const-qualified 变量在 C 中不被视为编译时常量,因此在某些情况下(例如,在声明数组大小时)不能使用。

enum 常量是编译时常量。对于整数值,IMO 通常更喜欢 enums 而不是 const 变量而不是 #define

【讨论】:

  • 我很震惊,唯一的答案正确地指出了在 C 中使用 const 的明显缺点(并且,通过扩展,Objective-C;请注意,这个问题与 C++ 无关! ) 没有升级到顶部。 +1,希望我能提供更多。
  • 所以,总结起来,简短的规则是:对于整数常量,使用匿名枚举。对于其他所有内容,请使用const。不要使用#define,除非是正确的宏或有条件包含的标记。
  • @Michael: enum 没有类型名的类型。例如,enum { foo = 42 }; 是匿名的; enum bar { foo = 42 }; 不是。
【解决方案2】:

其实定义这样的常量有三种方式,

  • 定义

  • 枚举
  • 常量变量

在 C 中,除非另有说明,否则一切都是 int。当我有许多相关的整数常量时,我​​更喜欢枚举。当您不关心值是什么时,枚举显然更可取。但即使您确实需要为所有常量指定值,我也喜欢枚举的心理分组。当您拥有类型时,代码会更好地记录自己,例如

Error MyFunc();

清楚地返回一组特定错误代码中的一个,而

int MyFunc()

可能会返回 Unix errno 的#define 列表之一,或者可能是其他东西,或者可能是那些加上一些特殊值——谁知道呢?如果你有不止一组返回码,这个函数使用哪一组?

更具体的枚举类型名称有助于编辑器中的标记功能、greps、调试等。

严格的 lint 可能会给您一些关于将枚举用作整数的警告,例如,如果您添加或或它们,或者将枚举传递给 int。

const 对象不同于 enum 或 #define,尤其是在 C 中。在 ANSI C 中,const int 与常规 int 一样占用空间;大多数编译器还会生成指向该地址的指针引用,而不是内联该值。因此,我很少在 C 中使用 const int。(C++ 的语义略有不同,因此那里的选择也不同。)

我曾经使用过的每个编译器都可以选择将枚举存储在尽可能小的空间中。通常它甚至是默认选项。为了在使用此类选项时强制使用更广泛的枚举,我通常会输入一个额外的无符号值:

typedef enum
{
    MyEnumA,
    MyEnumB,

    MyEnumForce16 = 7fff
} MyEnum;

与使用#define 的传统符号常量样式相比,使用枚举常量(enum)具有许多优点。这些优势包括更低的维护要求、更高的程序可读性和更好的调试能力。

1) 第一个优点是枚举常量由编译器自动生成。相反,符号常量必须由程序员手动赋值。

例如,如果您的程序中可能出现错误代码的枚举常量类型,那么您的枚举定义可能如下所示:

enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE_NOT_FOUND
};

在前面的示例中,编译器会自动为 OUT_OF_MEMORY 分配值 0(零),因为它首先出现在定义中。然后编译器继续自动为枚举常量分配数字,使 INSUFFICIENT_DISK_SPACE 等于 1,LOGIC_ERROR 等于 2,FILE_NOT_FOUND 等于 3,以此类推。 如果您要使用符号常量来处理相同的示例,您的代码将如下所示:

#define OUT_OF_MEMORY 0
#define INSUFFICIENT_DISK_SPACE 1
#define LOGIC_ERROR 2
#define FILE_NOT_FOUND 3

这两种方法中的每一种都得到相同的结果:四个常量分配了数值来表示错误代码。但是,如果要添加两个常量来表示错误代码 DRIVE_NOT_READYCORRUPT_FILE,请考虑所需的维护。使用枚举常量方法,您只需将这两个常量放在枚举定义中的任何位置。编译器将为这些常量生成两个唯一值。使用符号常量方法,您必须手动为这些常量分配两个新数字。此外,您还需要确保分配给这些常量的数字是唯一的。

2) 使用枚举常量方法的另一个优点是您的程序更具可读性,因此以后可能需要更新您的程序的其他人可以更好地理解。

3) 使用枚举常量的第三个优点是一些符号调试器可以打印枚举常量的值。相反,大多数符号调试器不能打印符号常量的值。这对调试程序有很大帮助,因为如果您的程序在使用枚举的行处停止,您可以简单地检查该常量并立即知道它的值。另一方面,由于大多数调试器无法打印#define 值,您很可能必须通过在头文件中手动查找该值来搜索该值。

#define 语句是一个预编译器指令。从技术上讲,任何以 # 开头的行都是预编译器执行的操作。预编译器将用其定义替换已定义令牌的所有实例。这样做:

#define DELAY 40
for (i=0;i<DELAY;i++) {
    for (j=0;j<DELAY;j++) {
        asm NOP;
    }
}

和这个完全一样(就编译器而言):

for (i=0;i<40;i++) {
    for (j=0;j<40;j++) {
        asm NOP;
    }
}

当编译器生成机器码时,它会看到数字 40 并使用立即寻址方式与累加器进行比较。数字 40 将与您引用它一样多次存储在代码中。在这种情况下,它是两次。下面是 CodeWarrior Ver5 生成的程序集:

7:    char i,j;
    8:    for (i=0;i<DELAY;i++) {
  0002 95       [2]             TSX   
  0003 7f       [2]             CLR   ,X
  0004          [5]     L4:     
    9:      for (j=0;j<DELAY;j++) {
  0004 6f01     [3]             CLR   1,X
  0006          [5]     L6:     
   10:        asm NOP;
  0006 9d       [1]             NOP   
  0007 6c01     [4]             INC   1,X
  0009 e601     [3]             LDA   1,X
  000b a128     [2]             CMP   #40  ;<---- notice opcode a1 and immediate constant 40, which is $28 in hexadecimal
  000d 25f7     [3]             BCS   L6
  000f 7c       [3]             INC   ,X
  0010 f6       [2]             LDA   ,X
  0011 a128     [2]             CMP   #40  ;<---- and here it is again.
  0013 25ef     [3]             BCS   L4
   11:      }
   12:    }
   13:  }

【讨论】:

  • +1 用于列出所有可用选项,但值得扩展使用 enum 的好处,以及与 enum#define 相比 const 的缺点。
【解决方案3】:

常量允许您指定数据类型,这(通常)是一个优势。宏更加灵活,因此如果您不小心,可能会给您带来更多麻烦。

最佳做法是尽可能使用常量,并且仅在您真正需要宏时才使用#define,而不仅仅是命名文字值。

【讨论】:

  • -1 没有提到 C 中的 const 不是真正的(编译时)常量,因此不能在 case 标签中使用,作为数组大小,以及在其他上下文中需要一个常量表达式。 #define 在 C 中被大量使用是有原因的(const 在 C++ 中使用得更频繁),并且掩盖它会导致这个问题的答案不好。
  • 我确实忘记了你提到的 const 在 C 中的弱点,但我认为最好的做法仍然是尽可能使用 const。
  • IMO,最好的选择是对那些整数常量表达式实际使用匿名enum - 它有点类型,有范围,没有名称冲突等,但它也是一个整数常量。对于其他所有内容(编译时并不重要 - 浮点数、字符串等),const 很好。另请注意,匿名枚举在 ANSI C89 中,尽管许多人认为相反(因为不存在匿名结构)。
  • enum 的另一个小缺点是编译器将删除任何数字后缀,如 U 或 LL,因为在标准 C 中枚举始终是 int(尽管一些编译器建议使用嵌入式软件的较小类型)。
  • 你能举一个“真的需要宏”的例子吗?
【解决方案4】:

常量具有被键入的优点,因此可以在编译时发现错误地使用它们。这对您来说可能无关紧要,但常量会占用内存空间,而#defines 则不会(因为它们在实际编译发生之前被替换)。

【讨论】:

  • 常量不需要占用内存空间,如果你不获取它们的地址(或以其他方式将它们视为左值)。任何理智的链接器都会优化任何这样的东西。
【解决方案5】:

常量遵循类型安全措施,#defines 被完全替换。同样正如 GMan 所说,#define 不尊重范围。

【讨论】:

    【解决方案6】:

    #define 的解释:#define 是立即数或宏。

    常量解释:常量是一个永远不会改变的任何类型的值。

    您可以删除指向 const 的指针,但不能删除 #define,尽管 #define 可以是指针 例如:#define ADDRESS ((int *)0x0012)

    那么为什么要使用常量如下:

    • 它们遵守语言的范围规则
    • 你可以在调试器中看到它们
    • 如果需要,您可以获取他们的地址
    • 如果需要,可以通过 const-reference 传递它们
    • 它们不会在您的程序中创建新的“关键字”。

    简而言之,const 标识符就像是语言的一部分,因为它们是语言的一部分。

    在模块中,如果没有声明指向常量的指针,C 编译器可以像 #define 一样优化 const。 在 CPU 方面, const 将成为“立即”值。 其他选择是 const 变量可以是 放置在代码区域而不是数据区域,因为它不会改变。 在某些机器上,如果您尝试通过指针修改常量,则将 ponter 声明为常量可能会导致异常。

    在某些情况下需要#define,但当您有选择时,通常应该避免使用它。您应该根据业务价值评估是使用 const 还是 #define:时间、金钱、风险。

    【讨论】:

      【解决方案7】:

      例如,Const 是一个可以获取其地址的对象。 它也是类型安全的,即编译器知道常量的类型是什么。 以上不适用于#define。

      【讨论】:

        【解决方案8】:
        1. const 产生一个左值,这意味着可以获取它的地址。 #define 没有。
        2. #define 可能会导致意外的宏扩展,这可能是要调试的 PITA。
        3. 正如其他人所提到的,#define 没有与之关联的类型。

        一般来说,对于我不需要使用它的任何事情,我都会避免使用像瘟疫一样的预处理器,主要是因为可能会意外扩展,而且用于缓解这种情况的 ALL_CAPS 约定非常丑陋。

        【讨论】:

          【解决方案9】:

          1) #define 可以被认为是独立于数据类型的可调参数,而常量允许我们提及数据类型。

          2) #define's 替换主程序中任何引用它们的代码。除此之外,我们甚至可以让宏函数执行特定任务,可以通过单独传递参数来调用它。在常量的情况下这些显然是不可能的。

          因此,它们是根据相关性使用的。

          【讨论】:

            【解决方案10】:

            使用define的好处是一旦你为exp定义了变量:#define NUMBER 30,main中的所有代码都将使用值为30的代码。如果你将30更改为40,它将直接改变main中的所有值使用这个变量(NUMBER)。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-07-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-05-18
              相关资源
              最近更新 更多