这是嵌入式系统"static const” vs “#define” in C"的情况。
在具有“传递”代码和模块的大/中项目中,为包含文件,模块等编写常量参数的最佳实践是什么?
在代码“pass-down”中,您不知道您选择的名称是在其他包含文件中定义的,还是可能在extern中调用,或者在可能包含您文件的其他文件中调用。
有以下三个选项:
static const int char_height = 12;
#define CHAR_HEIGHT 12
enum { char_height = 12 };
哪一个会更好(在内存限制未知的嵌入式系统上)?
原始代码主要使用#define
来实现这一点,但是这些常量是以几种方式随意实现的(并且在不同的位置甚至在相同的文件中),因为似乎有几个人为此开发了这个演示软件某个设备。
具体来说,这是一个演示代码,展示了某个设备的每个硬件和SDK功能。
我正在考虑的大多数数据都是用于配置环境的类型:屏幕尺寸,字符集特征,以及提高代码可读性的东西。不是编译器和预处理器可以执行的自动配置。但由于那里有很多代码,我担心全局名称冲突,我不愿意使用#define's
目前,我正在考虑从头开始重写项目并重新实现大部分已编写的函数以从一个c文件中获取常量或将常量的实现重新组织为一种样式会更好。
可是:
答案 0 :(得分:5)
始终考虑可读性和内存限制。此外,宏只是编译前发生的复制/粘贴操作。话虽如此,我想做以下事情:
static const
,如果要在一个c文件中使用它们(例如,不能跨多个文件全局访问)。在文件范围内,任何定义为const
的内容都应放在ROM中。显然,这些变量在初始化后无法更改。#define
定义所有常量值。enum
个词来增加可读性。您拥有固定值范围的任何地方我更喜欢枚举来明确说明意图。尝试使用面向对象的视角来处理项目(即使c不是OO)。隐藏私有函数(不要在头文件中创建原型),如果可以避免使用全局变量,请将变量仅标记为static
等一个c模块(文件)等。
答案 1 :(得分:3)
它们是3种不同的东西,应该在3种不同的情况下使用。
#define
应该用于需要在编译时进行评估的常量。一个典型的例子是静态分配的数组的大小,即
#define N 10
int x[N];
使用#define
所有常量也没关系,无论分配常数的方式和位置无关紧要。声称这样做不好的人只能表达他们自己的,个人的,主观的意见。
但是,当然,对于这种情况,您也可以使用const
个变量。 #define
和const
之间没有重要区别,但以下情况除外:
const
应该在哪个内存地址分配常量的情况下使用。它也应该用于程序员可能经常更改的变量。因为如果使用const
,则可以轻松地将变量移动到EEPROM或数据闪存中的内存段(但如果这样做,则需要将其声明为volatile)。
const
的另一个细微优势是,您的类型安全性比#define
更强。为了使#define
获得相同的类型安全性,您必须在宏中添加显式类型转换,这可能会更难以阅读。
当然,由于consts(和枚举)是变量,因此您可以使用static
关键字缩小其范围。这是一种很好的做法,因为这些变量不会使全局命名空间混乱。虽然全局命名空间中名称冲突的真正来源是由于命名策略不佳或根本没有命名策略导致的所有情况的99%。如果您不遵循编码标准,那么这就是问题的真正根源。
所以通常可以在需要时使常量变为全局,只要你有一个合理的命名策略(最好是属于一个代码模块的所有项应该共享相同的命名前缀),这是相当无害的实践。这不应该与将常规变量设为全局的做法相混淆,这总是一个非常糟糕的主意。
当您有多个彼此相关的常量值并且想要创建特殊类型时,仅才能使用枚举,例如:
typedef enum
{
OK,
ERROR_SOMETHING,
ERROR_SOMETHING_ELSE
} error_t;
枚举的一个优点是你可以使用经典技巧将枚举项的数量作为另一个编译时常量“免费”:
typedef enum
{
OK,
ERROR_SOMETHING,
ERROR_SOMETHING_ELSE,
ERRORS_N // the number of constants in this enum
} error_t;
但是枚举有各种各样的缺陷,所以应该谨慎使用它们。
枚举的主要缺点是它不是类型安全的,也不是“理智的”。首先,枚举常量(如上例中的OK
)始终类型int
,已签名。
枚举类型本身(在我的示例中为error_t
)可以是与char或int,signed或unsigned兼容的任何类型。猜一看,它是实现定义的,不可移植的。因此,您应该避免枚举,特别是作为各种数据字节映射的一部分或作为算术运算的一部分。
答案 2 :(得分:2)
我同意bblincoe ...... + 1
我想知道您是否理解该语法中的差异以及它如何/可能会影响实现。有些人可能不关心实现,但如果你要进入嵌入式,也许你应该这样做。
当bblincoe提到ROM而不是RAM时。
static const int char_height = 12;
理想情况下,这应该消耗.text房地产并预先确定具有您指定价值的房地产。作为const你不会改变它但它确实有一个占位符?现在为什么你需要一个常数的占位符?考虑到这一点,当然你可以出于某种原因破解二进制文件以打开或关闭某些东西或更改特定于电路板的调整参数......
如果没有易失性,但这并不意味着编译器必须始终使用.text位置,它可以优化并将该值直接作为指令放入,甚至更糟糕地优化数学运算并删除一些数学。
define和enum不消耗存储,它们是编译器选择如何实现的常量,最终那些位如果没有被优化掉,有时会在.text中的某个地方着陆,取决于指令集如何它的立即工作特定常数等。
因此,定义vs enum基本上是你想要选择所有值,还是希望编译器为你选择一些值,如果你想让编译器选择值,你可以定义是否要控制它。
所以它确实不是一个最佳实践的事情,它确定了你的程序需要做什么,并为这种情况选择合适的编程解决方案。
根据编译器和目标处理器的不同,选择volatile static const int与不这样做会影响rom消耗。但这是一个非常具体的优化,而不是一般的答案(与嵌入无关,但通常有编译)。
答案 3 :(得分:1)
Dan Saks解释了为什么他更喜欢这些文章Symbolic Constants和Enumeration Constants vs Constant Objects中的枚举常量。总之,请避免使用宏,因为它们不遵守通常的范围规则,并且通常不会为符号调试器保留符号名称。并且更喜欢枚举常量,因为它们不会受到可能影响常量对象的性能损失的影响。链接文章中有更多细节。
答案 4 :(得分:1)
考虑的另一件事是表现。 #define常量通常可以比const变量(对于整数)更快地访问,因为const需要从ROM(或RAM)中获取,而#define值通常是一个立即指令参数,因此它与指令(没有额外的周期)。
至于命名冲突,我喜欢使用像MOD_OPT_这样的前缀,其中MOD是模块名称OPT意味着define是编译时选项等。如果它们是'还只包括头文件中的#defines。重新使用公共API的一部分,否则在多个源文件中需要使用.inc文件,或者如果它们只是特定于该文件,则在源文件中定义它们。