“静态常量”与“#定义”与“枚举”


585

在下面的C语言语句中,哪个更好使用?

static const int var = 5;

要么

#define var 5

要么

enum { var = 5 };

35
有趣的是,这几乎是与stackoverflow.com/questions/1637332/static-const-vs-define相同的问题。唯一的区别是,这个问题是关于C ++的,而这个问题是关于C的。由于我的回答是C ++特定的,所以我说这使它们不相同,但其他人可能会不同意。
TED

53
完全不一样。出于兼容性原因,在很多领域C ++都允许C语法。在这种情况下,诸如“什么是做X的最佳方法”之类的问题在C ++中将有不同的答案。例如,对象初始化。
MSalters


这不是基于意见的吗?他们每个人都有不同的目的
山姆Hammamy

1
@RobertSsupportsMonicaCellio,是的。谢谢你的提示
Vijay

Answers:


690

这取决于您需要的价值。您(以及到目前为止的其他所有人)省略了第三种选择:

  1. static const int var = 5;
  2. #define var 5
  3. enum { var = 5 };

忽略有关名称选择的问题,然后:

  • 如果需要传递指针,则必须使用(1)。
  • 由于(2)显然是一个选项,因此您不需要传递指针。
  • (1)和(3)在调试器的符号表中都有一个符号-使调试更加容易。(2)更可能没有符号,让您想知道它是什么。
  • (1)不能用作全局范围内数组的维;(2)和(3)都可以。
  • (1)不能用作函数范围内的静态数组的维;(2)和(3)都可以。
  • 在C99下,所有这些都可以用于本地阵列。从技术上讲,使用(1)表示将使用VLA(可变长度数组),尽管'var'引用的尺寸当然会固定为5号。
  • (1)不能在switch语句之类的地方使用;(2)和(3)都可以。
  • (1)不能用于初始化静态变量;(2)和(3)都可以。
  • (2)可以更改您不想更改的代码,因为该代码已被预处理器使用;(1)和(3)都不会有这样的意外副作用。
  • 您可以检测是否在预处理器中设置了(2);(1)和(3)都不允许。

因此,在大多数情况下,更喜欢“枚举”而不是其他选择。否则,第一个和最后一个要点可能是控制因素-如果需要同时满足这两个条件,则必须加倍考虑。

如果您询问的是C ++,那么您每次都会使用选项(1)-静态const。


111
很棒的清单!缺点之一enum是它们被实现为int([C99] 6.7.2.2/3)。A #define可让您指定unsigned和long与UL后缀,并const指定类型。enum可能会导致通常的类型转换出现问题。
Gauthier

37
(2)人们总是抱怨类型安全。我从来不明白为什么不只使用“ #define var((int)5)”并欢呼您获得带有定义的类型安全性。
戈·布莱克曼

6
@RedX:您必须处于非常特殊的环境中才能关注空间。就是说,本身enum也不#define使用额外的空间。该值将作为指令的一部分出现在目标代码中,而不是被分配存储在数据段,堆或堆栈中。您将为分配一些空间static const int,但是如果您不占用地址,编译器可能会对其进行优化。
乔纳森·勒夫勒

15
enums(和static const)的另一个“投票” :它们不能更改。一个define可以是#undefine“d其中一个enumstatic const被固定到给定的值。
Daan Timmer 2014年

15
@QED:不,谢谢。一个简单的常数在括号外是安全的。或者,向我展示如何通过不带括号的5来更改可以合法编译的程序。如果它是函数样式宏的参数,或者表达式中包含任何运算符,那么如果我不包括括号,那么您应该正确地怪我。但这不是事实。
乔纳森·莱夫勒

282

一般来说:

static const

因为它尊重范围并且是类型安全的。

我唯一看到的警告是:是否希望在命令行上定义变量。还有一种选择:

#ifdef VAR // Very bad name, not long enough, too general, etc..
  static int const var = VAR;
#else
  static int const var = 5; // default value
#endif

只要有可能,请使用类型安全的替代方法来代替宏/省略号。

如果您确实需要使用宏(例如,您想要__FILE____LINE__),则最好非常小心地命名宏:以其命名约定 Boost建议使用大写字母,并以项目名称开头(此处为BOOST_ ),在仔细阅读库时,您会注意到,(通常)后面是特定区域(库)的名称,然后是有意义的名称。

通常,它使用冗长的名称:)


2
同意-使用#define时,由于预处理器不了解语法,因此也存在破坏代码的一般危险。
NeilDurant

10
使用#if比#ifdef更好,但是我同意。+1。
蒂姆·波斯特

58
这是标准的C ++福音。下面的答案更清楚地说明了选项的真正含义。特别是:我只是对“静态常量”有疑问。有人用它在头文件中定义了大约2000个“常量”。然后,此头文件包含在大约100个“ .c”和“ .cpp”文件中。=> 8兆字节的“常量”。大。是的,我知道您可能使用链接器删除了未引用的const,但是这仍然留下您所引用的“ const”。空间用完了,这个答案怎么了?
Ingo Blackman

2
@IngoBlackman:有了一个好的编译器,只有static地址被占用的那些才应该保留;如果采用该地址,则可能无法使用“ #define或” enum(无地址)...,所以我真的看不到可以使用哪种替代方法。如果您可以取消“编译时间评估”,那么您可能正在寻找extern const
Matthieu M.

15
@Tim Post:对于布尔标志,#if可能更可取#ifdef,但是在这种情况下,将无法从命令行定义varas 0。因此,在这种情况下,#ifdef只要0的合法价值就更有意义var
Maarten

108

特别是在C中?在C语言中,正确的答案是:使用#define(或在适当情况下,enum

虽然具有对象的作用域和键入属性是有益的,但const实际上const C对象(与C ++相反)不是真正的常数,因此在大多数实际情况下通常是无用的。

因此,在C语言中,选择应取决于计划使用常量的方式。例如,您不能将const int对象用作case标签(而宏将起作用)。您不能将const int对象用作位域宽度(而宏将起作用)。在C89 / 90中,您不能使用const对象指定数组大小(而宏将起作用)。即使在C99中,const当您需要非VLA时也无法使用对象指定数组大小数组。

如果这对您很重要,那么它将决定您的选择。大多数时候,您别无选择,只能#define在C中使用。别忘了另一种选择,它在C-中产生真常数enum

在C ++中,const对象是真正的常量,因此在C ++中,通常总是更喜欢const变体(无需显式)。static尽管C ++中)。


6
“您不能将const int对象用作大小写标签(而宏将起作用)” --->关于此语句,我在C中测试了const int变量,以使其处于工作状态....
约翰

8
@john:好,您需要提供您测试过的代码并命名特定的编译器。const int在所有版本的C语言中,在大小写标签中使用对象都是非法的。(当然,您的编译器可以自由地将其作为非标准的C ++语言扩展来支持。)
AnT 2012年

11
“ ... 因此在大多数实际情况下通常是无用的。” 我不同意。只要您不需要将名称用作常量表达式,它们就非常有用。C语言中的“常量”一词表示可以在编译时评估的内容;const表示只读。const int r = rand();是完全合法的。
基思·汤普森

在c ++中,constexprconst专门用于或的stl容器相比,最好使用。arraybitset
Mayukh Sarkar

1
@john,您必须在switch()语句中测试过,而不是case一个。我也刚好被这一个抓住了
Hi-Angel

32

static const和之间的区别在于,#define前者使用内存,而后者不使用内存进行存储。其次,您不能传递的地址,#define而可以传递的地址static const。实际上,这取决于我们所处的环境,我们需要在这两种情况中选择一种。两者在不同情况下都处于最佳状态。请不要以为一个优于另一个... :-)

如果真是 那样,丹尼斯·里奇本该保持最好的一个……哈哈哈... :-)


6
+1代表内存,一些嵌入式系统仍然没有那么多,尽管我可能会开始使用静态const并在需要时仅更改为#defines。
fluffyben 2012年

3
我刚刚测试过。实际上,与#define或enum相比,const int使用额外的内存。由于我们对嵌入式系统进行编程,因此我们无法承受额外的内存使用量。因此,我们将回到使用#define或enum。
Davide Andrea

2
实际上,const使用内存确实是(不再)事实。const int使用-O3时,GCC(已在4.5.3和一些较新版本中进行了测试)可轻松地将代码优化为直接文字。因此,如果您进行低RAM嵌入式开发(例如AVR),并且使用GCC或其他兼容的编译器,则可以安全地使用C const。我还没有测试过,但是希望Clang可以做同样的事情。
拉斐尔2015年

19

在C #define中更受欢迎。您可以使用这些值来声明数组大小,例如:

#define MAXLEN 5

void foo(void) {
   int bar[MAXLEN];
}

static const据我所知,ANSI C不允许您在此上下文中使用。在C ++中,在这种情况下应避免使用宏。你可以写

const int maxlen = 5;

void foo() {
   int bar[maxlen];
}

甚至省去了,static因为const已经暗示了内部链接(仅在C ++中)。


1
“内部联系”是什么意思?我可以const int MY_CONSTANT = 5;在一个文件extern const int MY_CONSTANT;中使用另一个文件。我在标准(至少C99)中找不到有关const更改默认行为的任何信息“ 6.2.2:5如果对象的标识符声明具有文件范围且没有存储类指定者,则其链接是外部的”。
Gauthier

@Gauthier:对不起。我应该说“ C语言已经由const隐含了”。这是特定于C ++的。
sellibitze 2011年

@sellibitze它很高兴看到一些参数沿途而不是吨的意见如果将真正的参数奖金,你说对了!
保罗

1
从C99开始,您的第二个片段是合法的。bar是VLA(可变长度数组);编译器可能会生成代码,就好像其长度是恒定的一样。
Keith Thompson

14

constC语言的另一个缺点是您不能在初始化另一个值时使用该值const

static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;

// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND 
                                     * NUMBER_OF_HANDS;

即使这对于const也无效,因为编译器不会将其视为常量:

static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!

const在这些情况下,我很乐意使用typed ,否则...


5
游戏有点晚了,但是这个问题又来了。追逐为什么static uint8_t const ARRAY_SIZE = 16;突然不再编译的过程可能会有些挑战,特别是当将#define ARRAY_SIZE 256其埋在纠缠的标头网中十层深的地方时。所有大写字母的名字ARRAY_SIZE都在麻烦。为宏保留ALL_CAPS,不要定义非ALL_CAPS形式的宏。
David Hammen

@David:声音忠告,我将遵循。
Gauthier

1
4年后,您为我节省了很多时间,弄清楚为什么我不能“嵌套” const。这可能需要更多支持!
普洛夫2015年

11

如果可以摆脱它,static const则具有很多优势。它遵循正常的作用域原则,在调试器中可见,并且通常遵循变量遵循的规则。

但是,至少在最初的C标准中,它实际上不是常数。如果使用#define var 5,则可以将其编写int foo[var];为声明,但不能这样做(除非作为编译器扩展名,否则使用static const int var = 5;。)在C ++中不是这种情况,因为该static const版本可以在该版本可以使用的任何地方使用#define,我相信C99也是如此。

但是,切勿#define使用小写字母命名常量。在翻译单元末尾之前,它将覆盖该名称的所有可能用法。宏常量应该有效地位于它们自己的名称空间中,该名称空间传统上是所有大写字母,可能带有前缀。


6
不幸的是,C99并非如此。const在C99中,仍然不是一个真正的常数。您可以const在C99 中用声明数组大小,但这仅是因为C99支持可变长度数组。因此,它仅在允许VLA的地方起作用。例如,即使在C99中,您仍然无法使用const来在中声明成员数组的大小struct
AnT

尽管C99不会允许您这样做是正确的,但GCC(经过4.5.3测试)可以完美地使您初始化const int大小与C ++ const或宏一样的数组。是否要依赖GCC与标准的这种偏离当然是您的选择,除非您能真正预见使用GCC或Clang以外的其他编译器,否则我会亲自使用它,后者在此处具有相同的功能(已通过Clang测试) 3.7)。
拉斐尔2015年

7

始终最好使用const而不是#define。这是因为const由编译器处理,而#define由预处理器处理。就像#define本身不是代码的一部分(大致而言)。

例:

#define PI 3.1416

符号名PI可能永远不会被编译器看到。甚至在源代码到达编译器之前,预处理器可能会将其删除。结果,名称PI可能不会输入到符号表中。如果在编译过程中遇到涉及使用常量的错误,这可能会造成混淆,因为错误消息可能引用的是3.1416,而不是PI。如果PI是在您未编写的头文件中定义的,则您将不知道3.1416的来源。

这个问题也会在符号调试器中出现,因为同样,您正在使用的名称可能不在符号表中。

解:

const double PI = 3.1416; //or static const...

6

#define var 5如果您有这样的事情将会给您带来麻烦mystruct.var

例如,

struct mystruct {
    int var;
};

#define var 5

int main() {
    struct mystruct foo;
    foo.var = 1;
    return 0;
}

预处理器将替换它,并且代码将无法编译。因此,传统的编码方式建议所有常量#defines都使用大写字母以避免冲突。


6

我编写了快速测试程序来演示一个区别:

#include <stdio.h>

enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};

#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32

int main(int argc, char *argv[]) {

   printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);

   return(0);
}

这将与以下错误和警告一起编译:

main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
      ^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
      ^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
        ^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
        ^

请注意,当define给出警告时,enum给出错误。


4

定义

const int const_value = 5;

并不总是定义一个常数。一些编译器(例如tcc 0.9.26)只是分配以名称“ const_value”标识的内存。使用标识符“ const_value”,您不能修改此存储器。但是您仍然可以使用另一个标识符来修改内存:

const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.

这意味着定义

#define CONST_VALUE 5

是定义不能以任何方式修改的常数值的唯一方法。


8
使用指针修改常量值是未定义的行为。如果您愿意,#define也可以通过编辑机器代码进行修改。
ugoren 2013年

您部分正确。我使用Visual Studio 2012测试了代码,然后将其打印出来5。但是一个人不能修改,#define因为它是一个预处理器宏。它在二进制程序中不存在。如果要修改所有CONST_VALUE使用过的地方,则必须一一修改。
user2229691 2013年

3
@ugoren:假设您先编写#define CONST 5,然后编写,然后if (CONST == 5) { do_this(); } else { do_that(); }编译器将删除该else分支。您如何建议将机器代码编辑CONST为6?
基思·汤普森

@KeithThompson,我从未说过它可以轻松可靠地完成。只是那#define不是防弹的。
ugoren

3
@ugoren:我的观点是,“编辑机器代码”不是复制改变a值的影响的明智方法#define。唯一的真正方法是编辑源代码并重新编译。
基思·汤普森

4

尽管问题是关于整数的,但值得注意的是,如果需要恒定的结构或字符串,则#define和enum是无用的。这些通常都作为指针传递给函数。(使用字符串是必需的;使用结构则效率更高。)

对于整数,如果您处于内存非常有限的嵌入式环境中,则可能需要担心常量的存储位置以及对常量的访问方式。编译器可能会在运行时添加两个const,但在编译时添加两个#define。可以将#define常数转换为一个或多个MOV [立即]指令,这意味着该常数有效地存储在程序存储器中。常量将存储在数据存储器的.const节中。在具有哈佛体系结构的系统中,性能和内存使用量可能会有所不同,尽管它们可能很小。它们对于内部循环的硬核优化可能很重要。


3

不要以为“永远是最好的”是有答案的,但是正如Matthieu所说

static const

是类型安全的。#define不过,我最大的烦恼是,在Visual Studio中进行调试时,您无法监视该变量。出现错误,找不到该符号。


1
“您不能观看变量”对,它不是变量。它没有改变,为什么您需要观看?您只需搜索标签,便可以在任何使用位置找到它。为什么您需要(甚至想要)观看#define?
马歇尔·尤班克斯

3

顺便说一下,#define提供了适当作用域但其行为类似于“真实”常量的替代方法是“枚举”。例如:

enum {number_ten = 10;}

在许多情况下,定义枚举类型并创建这些类型的变量很有用;如果这样做,调试器可能能够根据其枚举名称显示变量。

但是,这样做的一个重要警告是:在C ++中,枚举类型与整数的兼容性有限。例如,默认情况下,不能对它们执行算术运算。我发现这对于枚举是一种奇怪的默认行为。尽管有一个“严格的枚举”类型会很好,但考虑到C ++通常与C兼容的愿望,我认为“枚举”类型的默认行为应该可以与整数互换。


1
在C语言中,枚举常量始终为type int,因此“ enum hack”不能与其他整数类型一起使用。(枚举类型不一定与某些实现定义的整数类型兼容int,但在这种情况下,该类型是匿名的,因此无关紧要。)
Keith Thompson14年

@KeithThompson:自从我写了上面的文章以来,我读到如果编译器将类型分配int给枚举类型的变量以外的类型(允许编译器执行),并且试图将其分配给这样的变量,则MISRA-C会发出嘶哑的声音自己枚举的成员。我希望标准委员会会添加一些可移植的方法来声明具有指定语义的整数类型。 任何平台,无论char大小,都应该能够声明一个将包装mod 65536的类型,即使编译器必须添加很多AND R0,#0xFFFF或等效的指令也是如此。
supercat

您可以使用uint16_t,尽管这当然不是枚举类型。最好让用户指定用于表示给定枚举类型的整数类型,但是对于单个值,使用typedeffor uint16_t和一系列#defines 可以达到很多相同的效果。
基思·汤普森

1
@KeithThompson:我了解出于历史原因,我们坚持认为某些平台的评估结果2U < -1L为true,而另一些平台的评估结果为false,而我们现在仍然坚持这样的事实,即某些平台将实现uint32_tint32_t签名之间的比较还有一些是未签名的,但这并不意味着委员会无法定义C的向上兼容的后继程序,该后继程序包括其语义在所有编译器上都一致的类型。
supercat 2014年

1

一个简单的区别:

在预处理时,常数将替换为其值。因此,您不能将取消引用运算符应用于定义,但是可以将取消引用运算符应用于变量。

如您所愿,define比静态const更快。

例如,具有:

#define mymax 100

你做不到printf("address of constant is %p",&mymax);

但是有

const int mymax_var=100

你可以做printf("address of constant is %p",&mymax_var);

更清楚地说,在预处理阶段,定义将被其值替换,因此程序中没有存储任何变量。我们只有使用定义的程序文本段中的代码。

但是,对于静态const,我们有一个分配在某处的变量。对于gcc,静态const分配在程序的文本段中。

上面,我想介绍一下引用运算符,所以用引用替换取消引用。


1
您的回答是非常错误的。这是关于C的,您的答案与C ++有关,C ++的const限定词语义非常不同。C除了枚举常量之外没有符号常量。A const int是变量。您还会混淆语言和特定的实现。无需放置对象。对于gcc甚至都不是这样:通常,它将const合格的变量放在该.rodata部分中。但这取决于目标平台。您的意思是操作员的地址&
对此网站来说太老实了

0

我们查看了MBF16X上产生的汇编代码...两种变体都为算术运算提供了相同的代码(例如,ADD Instant)。

因此const int,对于#define旧样式,最好进行类型检查。也许是特定于编译器的。因此,请检查您生成的汇编代码。


-1

我不确定我是否正确,但是我认为调用#defined值比调用任何其他通常声明的变量(或const值)要快得多。这是因为在程序运行时,它需要使用一些通常声明的变量,因此需要跳转到内存中的确切位置以获取该变量。

相反,当它使用#defined值时,程序无需跳转到任何已分配的内存,它只需获取该值。如果#define myValue 7和程序一起调用myValue,则其行为与刚调用时的行为完全相同7

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.