"static const"与"#define"与"enum"的区别

665

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

static const int var = 5;
或者
#define var 5
或者
enum { var = 5 };

51
有趣的是,这个问题与https://dev59.com/DnI-5IYBdhLWcg3w3ck8几乎完全相同。唯一的区别在于那个问题是关于C++的,而这个问题是关于C的。由于我的答案是特定于C ++的,我认为这使它们不同,但其他人可能不同意。 - T.E.D.
69
肯定不完全相同。出于兼容性原因,C++ 允许使用 C 语法的很多领域。在这些情况下,“如何最好地实现 X”这样的问题在 C++ 中将有不同的答案,比如对象初始化。 - MSalters
1
另外,https://dev59.com/4XI-5IYBdhLWcg3wQV-c - jamesdlin
1
这怎么不是基于观点的?它们各自有不同的目的。 - Sam Hammamy
17个回答

5
定义:
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

常量是一种唯一的方式,可以定义一个数值,该数值不可被任何方式修改。


10
使用指针修改常量值是未定义行为。如果愿意这样做,#define也可以通过编辑机器代码进行修改。 - ugoren
你说的部分正确。我用Visual Studio 2012测试了代码,它打印出“5”。但是不能修改#define,因为它是预处理器宏。它在二进制程序中不存在。如果想要修改所有使用CONST_VALUE的地方,必须逐个修改。 - user2229691
3
假设您编写了 #define CONST 5,然后编写了 if (CONST == 5) { do_this(); } else { do_that(); } 的代码,并且编译器消除了 else 分支。如果要将 CONST 更改为 6,您需要编辑机器码。 - Keith Thompson
@KeithThompson,我从未说过这可以轻松可靠地完成。只是#define不是万无一失的。 - ugoren
3
我的观点是,“编辑机器码”不是复制更改#define值效果的明智方式。唯一真正的方法是编辑源代码并重新编译。 - Keith Thompson

5
虽然问题是关于整数的,但值得注意的是,如果你需要一个常量的结构或字符串,#define和枚举是无用的。这两者通常作为指针传递给函数。(对于字符串来说是必需的;对于结构来说更加高效。)
至于整数,如果你处于内存非常有限的嵌入式环境中,你可能需要担心常量存储的位置以及对它的访问如何编译。编译器可能会在运行时添加两个常量,但在编译时添加两个#define。一个#define常量可能会被转换为一个或多个MOV [immediate]指令,这意味着常量实际上存储在程序内存中。一个const常量通常会存储在数据内存的一个单独部分,比如.const或.rodata。在具有哈佛架构的系统中,性能和内存使用可能会有差异,尽管它们可能很小。它们可能对内部循环的硬核优化有影响。

Re ".const section in data memory.": 这似乎是系统特定的。是哪个系统?使用的是什么汇编器? - Peter Mortensen
我在TI的编译器和GCC中都见过,而且在其他平台上似乎也很常见。我已经修改了这个句子,以明确指出这只是一个例子,不是普遍规则。 - Adam Haun

3

我不认为有一个“永远最好”的答案,但正如Matthieu所说的那样

static const

是类型安全的。然而,我对#define最大的抱怨是在Visual Studio中进行调试时无法查看变量。它会出现找不到符号的错误。


1
你不能观察这个变量。没错,它不是一个变量。它不会改变,你为什么需要观察它呢?你可以通过搜索标签来找到它在哪里被使用。你为什么需要(甚至想要)观察一个 #define 呢? - Marshall Eubanks

2
顺便提一下,提供适当范围但行为类似于“真实”常量的 #define 替代方案是 "enum"。例如:
enum {number_ten = 10};

在许多情况下,定义枚举类型并创建该类型的变量非常有用;如果这样做,调试器可以根据它们的枚举名称显示变量。
然而,需要注意的一点是:在C++中,枚举类型与整数的兼容性有限。例如,默认情况下不能对它们进行算术运算。我觉得这是枚举类型的一个奇怪的默认行为;虽然拥有一个"strict enum"类型会很好,但考虑到C++要与C通用兼容的愿望,我认为"enum"类型的默认行为应该与整数可互换。

1
在C语言中,枚举常量始终是int类型的,因此"枚举黑客技巧"不能与其他整数类型一起使用。(枚举类型与某些实现定义的整数类型兼容,不一定是int类型,但在本例中该类型是匿名的,所以无关紧要。) - Keith Thompson
@KeithThompson:自我写上述内容以来,我已经阅读到MISRA-C会在编译器将枚举类型变量分配给除'int'之外的类型(允许编译器这样做)并尝试将其成员分配给这样的变量时发出警报。我希望标准委员会能够添加声明具有指定语义的整数类型的可移植方式。任何平台,无论'char'大小如何,都应该能够声明一种类型,该类型将包装mod 65536,即使编译器必须添加大量'AND R0,#0xFFFF'或等效指令。 - supercat
您可以使用uint16_t,但当然这不是一个枚举类型。让用户指定用于表示给定枚举类型的整数类型会很好,但是您可以通过为每个值定义一个#define和对于uint16_ttypedef系列来实现相同的效果。 - Keith Thompson
1
@KeithThompson:我知道由于历史原因,我们现在面临这样一个事实,即某些平台将2U < -1L评估为真,而其他平台则评估为假。我们现在也面临这样一个事实,即一些平台将uint32_tint32_t之间的比较实现为有符号的,而另一些平台则实现为无符号的。但这并不意味着委员会不能定义一个向上兼容的C语言后继版本,其中包括类型的语义在所有编译器上都是一致的。 - supercat
我犯了一个小错误:C++的枚举类型与任何整数类型不兼容(而C的枚举类型是兼容的),但是C++的枚举类型具有与其"底层类型"相同的表示方式,并且枚举表达式可以用于算术表达式中。至少从C++98开始就是这样。enum class是在C++11中引入的,但普通枚举没有改变。你可能使用的是有问题或不符合规范的编译器吗?无论如何,对于当前的C++(至少从1998年以来)和任何人可能使用的C++编译器,你的答案都是不正确的。 - Keith Thompson
显示剩余6条评论

1
一个简单的区别:
在预处理阶段,常量会被替换为它的值。因此你不能对定义使用取址运算符,但是你可以对变量使用取址运算符。
正如你所想象的那样,define 比 static const 更快。
例如,有以下代码:
#define mymax 100

您不能执行printf("常量地址为%p", &mymax);

但是,有一个

const int mymax_var=100

你可以使用printf("常量的地址为%p", &mymax_var);

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

然而,对于静态常量,我们有一个被分配在某个地方的变量。对于gcc,静态常量被分配在程序的文本段中。

上面,我想讲述引用运算符,所以将解引用替换为引用。


1
你的答案非常错误。这是关于C语言的问题,而你的答案涉及到C++,它对于const限定符有非常不同的语义。C语言除了枚举常量之外没有符号常量。一个const int是一个变量。你还混淆了语言和具体实现。没有要求放置对象的位置。甚至对于gcc来说也不是真的:通常它将const限定的变量放在.rodata节中。但这取决于目标平台。你指的是取地址运算符& - too honest for this site

0

我们查看了MBF16X上生成的汇编代码... 两种变体在算术运算(例如ADD Immediate)方面产生相同的代码。

因此,对于类型检查,首选const int而不是旧式的#define。也许这取决于编译器。因此,请检查您生成的汇编代码。


-2
我不确定是否正确,但在我看来,调用`#define`定义的值要比调用其他正常声明的变量(或常量值)快得多。
这是因为当程序运行并需要使用一些正常声明的变量时,它需要跳转到内存中的确切位置来获取该变量。
相反,当它使用`#define`定义的值时,程序不需要跳转到任何分配的内存;它只需取值。如果`#define myValue 7`并且程序调用`myValue`,它的行为与仅调用`7`时完全相同。

1
请问您能否量化这个说法更快的陈述?您是否以某种方式进行了分析?在哪个环节更快 - 编译时间?链接时间?运行时?在哪些系统和编译器上? - braindigitalis
实际上,就像我一开始说的那样,“我不确定我是否正确,但在我看来...”。我更希望从更有经验的用户那里得到任何验证。 - pajczur

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