#define和常量的优缺点是什么?

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

9
#define指令不遵循作用域。 - GManNickG
所选择的重复项是C语言。Objective-C对应的是*文件范围内可变修改的数组*。 - Peter Mortensen
10个回答

26

正如0A0D所提到的,在C语言中有 #definesenumsconst 变量。值得注意的是,在C语言中带有 const 修饰的变量不被视为编译时常量,因此在某些情况下不能使用(例如在声明数组大小时)。

enum 常量是编译时常量。对于整型数值而言,个人认为最好优先选择 enums 而不是 const 变量或者 #define


3
我很震惊,唯一一个正确指出在C语言(以及通过推广也适用于Objective-C;请注意,这个问题不是关于C++的!)使用“const”明显有劣势的答案并没有被置顶。给它点赞,希望我能多给一些赞。 - Pavel Minaev
5
简而言之,对于整型常量,请使用匿名枚举。而对于其他情况,请使用const。除了适当的宏或有条件包含的标记外,请不要使用#define - Pavel Minaev
1
@Michael:没有类型名称的enum类型。例如,enum { foo = 42 };是匿名的;而enum bar { foo = 42 };则不是。 - jamesdlin

18

事实上,有三种定义这样的常量的方法:

  • 宏定义

  • 枚举类型
  • const 变量

在 C 中,除非另有规定,否则一切皆为 int 类型。当我有一些相关的整数常量时,我更喜欢使用枚举类型。当你不关心值是什么时,枚举类型显然更好。但即使你确实需要为所有常量指定值,我也喜欢枚举类型的概念上的分组。当你有了类型后,代码自己就能更好地记录下来。

Error MyFunc();

clearly返回特定一组错误代码之一,而

int MyFunc()

可能会返回Unix errno的# define列表之一,或者可能是其他内容,或者可能是这些加上一些特殊值 - 谁知道呢?如果您有多个返回代码集,则此函数使用哪个集合?

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

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

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

我使用过的每个编译器都有将枚举存储在最小空间中的选项。通常甚至是默认选项。当使用这种选项时,为了强制使用更宽的枚举,我通常会添加额外的无符号值:

typedef enum
{
    MyEnumA,
    MyEnumB,

    MyEnumForce16 = 7fff
} MyEnum;

使用枚举常量(enum)相比于使用传统的符号常量风格 #define 有许多优点。这些优点包括更低的维护要求、改善程序可读性和更好的调试能力。

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:  }

2
列出所有可用选项是一个好的做法,但值得进一步扩展使用enum的优点和与const相比的劣势,以及与#define相比的劣势。 - Pavel Minaev

17

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

最佳实践是尽可能多地使用常量,并仅在确实需要宏时才使用# define,而不仅仅是命名字面值。


18
在回答这个问题时,没有提到 C 中的const不是真正的(编译时)常数,并且不能用于 case 标签、数组大小和其他需要常量表达式的上下文中。这就是为什么 C 中经常使用 #define(而在 C++ 中更经常使用 const)的原因,忽略它会导致糟糕的答案。 - Pavel Minaev
1
我忘记了你提到的C语言中const关键字的弱点,但我认为最佳实践仍然是尽可能使用const。 - Asher Dunn
6
在我看来,最好的选择是使用匿名的enum来表示整数常量表达式。这种方法有类型、作用域和避免命名冲突等优点,同时也是整数常量。对于其他类型(如浮点数、字符串等)不需要编译时计算的常量,使用const就可以了。请注意,即使许多人认为没有匿名结构体,但匿名enum在ANSI C89中是存在的。 - Pavel Minaev
1
enum 的另一个小缺点是,任何数字后缀(如 U 或 LL)都将被编译器删除,因为在标准 C 中,枚举始终是 int 类型(尽管一些编译器建议在嵌入式软件中使用较小的类型)。 - calandoa
你能举个“真正需要宏”的情况的例子吗? - Arc676

4

常量的优点是有类型,因此在编译时使用不正确的常量可以被发现。也许这对你来说并不重要,但常量占用内存空间,而#defines不占用(因为它们在实际编译之前被替换)。


3
如果您不获取常量的地址(或以其他方式将它们视为左值),常量就不需要占用内存空间。任何明智的链接器都会对此进行优化处理。 - Pavel Minaev

3
常量遵循类型安全规则,而#define则直接替换。正如GMan所说,#define不尊重作用域。

1

#define的解释:#define是一个立即值或宏定义。

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

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

那么我们为什么要使用常量呢?原因如下:

  • 它们遵循语言的作用域规则
  • 您可以在调试器中查看它们
  • 如果需要,您可以获取它们的地址
  • 如果需要,您可以通过const引用传递它们
  • 它们不会在程序中创建新的“关键字”

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

在一个模块中,如果没有指针声明到常量,C编译器可以将const优化为#define。在CPU术语中,const将变成“立即”值。另一种选择是,由于const不会改变,它可以被放置在代码区而不是数据区。在某些机器上,声明指向常量的指针可能会导致异常,如果您尝试通过指针修改常量。 有时需要使用#define,但通常应该在有选择的情况下避免使用它。您应该根据业务价值(时间、金钱、风险)来评估是否使用const或#define。

0

Const 是一个对象,你可以取它的地址,例如。而且它是类型安全的,也就是编译器知道常量的类型是什么。但是这并不适用于 #define。


0
  1. const 产生一个左值,这意味着它的地址可以被取得。而 #define 则不行。
  2. #define 可能会导致无意中宏扩展,这可能很难调试。
  3. 正如其他人所提到的,#define 没有与之关联的类型。

总的来说,我会尽量避免使用预处理器,因为可能会出现意外扩展,而且 ALL_CAPS 的惯例非常丑陋。


0
1)#define可以被视为与数据类型无关的可调参数,而常量允许我们指定数据类型。
2)#define会替换主程序中引用它的任何代码。此外,我们甚至可以使用宏函数执行特定任务,只需传递参数即可。这在常量的情况下显然是不可能的。
因此,它们的使用取决于相关性。

0

使用define的好处是,一旦您为变量定义了值,例如:#define NUMBER 30,主函数中的所有代码都将使用该值为30的代码。如果您将30更改为40,则会直接更改使用此变量(NUMBER)的主函数中的所有值。


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