如何在C中解析链接的宏?

时间:2015-12-15 19:18:13

标签: c macros const c-preprocessor

如果我想使用预处理器#define语句来轻松定义和计算常量和公共函数,并利用较少的RAM开销(而不是使用const值)。但是,如果一起使用多个宏,我不确定它们是如何解决的。

我正在设计我自己的DateTime代码处理,类似于linux时间戳,但对于一个代表1/60秒的刻度更新的游戏。我宁愿声明链接的值,但想知道硬编码值是否会表现得更快。

#include <stdint.h>

// my time type, measured in 1/60 of a second.
typedef int64_t DateTime;

// radix for pulling out display values
#define TICKS_PER_SEC  60L
#define SEC_PER_MIN    60L  
#define MIN_PER_HR     60L
#define HRS_PER_DAY    24L
#define DAYS_PER_WEEK   7L
#define WEEKS_PER_YEAR 52L

// defined using previous definitions (I like his style, write once!)
#define TICKS_PER_MIN    TICKS_PER_SEC * SEC_PER_MIN
#define TICKS_PER_HR     TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR
#define TICKS_PER_DAY    TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY
// ... so on, up to years

//hard coded conversion factors.
#define TICKS_PER_MIN_H    3600L      // 60 seconds = 60^2 ticks
#define TICKS_PER_HR_H     216000L    // 60 minutes = 60^3 ticks
#define TICKS_PER_DAY_H    5184000L   // 24 hours   = 60^3 * 24 ticks

// an example macro to get the number of the day of the week
#define sec(t)((t / TICKS_PER_DAY) % DAYS_PER_WEEK)

如果我使用sec(t)宏,该宏使用由前面3个宏TICKS_PER_DAY定义的TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY,则会在我调用sec(t)的代码中的任何位置执行此操作:< / p>

(t / 5184000L) % 7L)

或每次扩展到:

(t / (60L * 60L * 60L * 24L)) % 7L)

以便在每一步执行额外的乘法指令?这是宏和const变量之间的权衡,还是我误解了预处理器的工作原理?

更新:

根据许多有用的答案,将宏扩展为常量表达式的最佳设计是将定义包装在括号

1。正确的操作顺序:

(t / 60 * 60 * 60 * 24) != (t / (60 * 60 * 60 * 24))

2。通过将常量值组合在一起来鼓励编译器不断折叠:

// note parentheses to prevent out-of-order operations
#define TICKS_PER_MIN    (TICKS_PER_SEC * SEC_PER_MIN)
#define TICKS_PER_HR     (TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR)
#define TICKS_PER_DAY    (TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY)

4 个答案:

答案 0 :(得分:2)

预处理器只进行文本替换。它将使用“额外”乘法来评估第二个表达式。编译器通常会尝试优化常量之间的算术,但是,只要它可以这样做而不改变答案。

为了最大化优化的机会,您需要注意保持常量“彼此相邻”,以便它可以看到优化,尤其是浮点类型。换句话说,如果t是变量,您希望30 * 20 * t代替30 * t * 20

答案 1 :(得分:1)

请参阅gcc preprocessor macro docs,特别是类似对象的宏

我认为编译器也在这里发挥作用。例如,如果我们只考虑预处理器,那么它应该扩展到

(t / (60L * 60L * 60L * 24L)) % 7L)

但是,编译器(无论优化?)可能会将其解析为

(t / 5184000L) % 7L)

因为它们是独立的常量,因此代码执行速度更快/更简单。

注意1:您应该在定义中使用“(t)”来防止意外的扩展/解释。 注意2:另一个最佳做法是避免使用undef,因为这会降低代码的可读性。请参阅有关宏扩展如何受此影响的说明(类似对象的宏部分)。

更新:来自Object-like Macros部分:

  

当预处理器扩展宏名称时,宏的扩展将替换宏调用,然后检查扩展以查找更多要扩展的宏。例如,

     

#define TABLESIZE BUFSIZE #define BUFSIZE 1024 TABLESIZE ==> BUFSIZE ==> 1024   首先扩展TABLESIZE以生成BUFSIZE,然后扩展该宏以生成最终结果1024。

     

请注意,在定义TABLESIZE时未定义BUFSIZE。 TABLESIZE的'#define'完全使用您指定的扩展 - 在本例中为BUFSIZE - 并且不检查它是否也包含宏名称。 仅当您使用TABLESIZE时,才会扫描其扩展名以获取更多宏名称。

(强调我的)

答案 2 :(得分:1)

宏扩展只不过是简单的文本替换。在宏扩展之后,编译器将解析结果并执行其通常的优化,其中应包括常量折叠。

但是,这个例子说明了初学者在用C语言定义宏时常犯的错误。如果宏要扩展为表达式,那么好的C语法规定如果结果本身包含暴露,则值应始终包含在括号中运营商。在此示例中,查看TICKS_PER_DAY

的定义
#define TICKS_PER_DAY    TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY

现在看一下sec(注意分号不应该出现,但我现在暂时忽略它):

#define sec(t)((t / TICKS_PER_DAY) % DAYS_PER_WEEK);

如果将其实例化为sec(x),则会扩展为:

((x / 60L * 60L * 60L * 24L) % 7L);

这显然不是预期的。它只会除以最初的60L,之后剩余的值将成倍增加。

解决此问题的正确方法是修复TICKS_PER_DAY的定义以正确封装其内部操作:

#define TICKS_PER_DAY    (TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY)

当然,sec应该是一个表达式宏,不应该包含分号,这会阻止它被使用,例如,在sec(x) + 10这样的上下文中:

#define sec(t)  ((t / TICKS_PER_DAY) % DAYS_PER_WEEK)

现在让我们看看如何通过这些错误修复来扩展sec(x)

((x / (60L * 60L * 60L * 24L)) % 7L)

现在这实际上会按预期进行。编译器应该对乘法进行常数折叠,在单个除法中进行弹性,然后是单个mod。

编辑:看起来丢失的括号已添加到原始帖子中。如果没有它们,根本就行不通。另外的分号也已从原帖中删除。

答案 3 :(得分:1)

它扩展为:

(t / (60L * 60L * 60L * 24L)) % 7L)

这是因为宏由预处理器处理,它只是将宏扩展为它们的值(必要时递归)。

但这并不意味着在你使用sec(t)的每个点重复整个计算。这是因为计算在编译时发生。所以你不要在运行时付出代价。编译器预先计算这种常量计算,并在生成的代码中使用计算值。