简体   繁体   English

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

[英]How are chained macros resolved in C?

If I want to use preprocessor #define statements for easy definition and calculation of constants and common functions and take advantage of less RAM overhead (as opposed to using const values). 如果我想使用预处理器#define语句来轻松定义和计算常量以及常用函数,并利用较少的RAM开销(与使用const值相反)。 However, I am unsure as to how they are resolved if many macros are used together. 但是,如果同时使用许多宏,我不确定如何解决它们。

I'm designing my own DateTime code handling, similar to linux timestamps but for a game with tick updates that represent 1/60th of a second. 我正在设计自己的DateTime代码处理,类似于Linux时间戳,但是对于带有滴答更新的游戏来说,该更新代表了1/60秒。 I would prefer to declare values chained, but wonder if hard coded valued would perform faster. 我宁愿声明链接的值,但想知道硬编码的值是否会执行得更快。

#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)

If I use the sec(t) macro, which uses TICKS_PER_DAY which is defined by 3 previous macros TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY , does that place everywhere in my code that calls sec(t) : 如果我使用sec(t)宏,它使用TICKS_PER_DAY这是由前3个宏定义TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY ,确实那个地方到处都在我的代码的呼叫sec(t)

(t / 5184000L) % 7L)

or does it expand out each time to: 还是每次都扩展到:

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

so that extra multiplication instructions are executed at each step? 以便在每个步骤执行额外的乘法指令? Is this a tradeoff between macros and const variables, or do I misunderstand how the preprocessor works? 这是宏和const变量之间的折衷,还是我误解了预处理器的工作方式?

UPDATE: 更新:

Per many helpful answers, the best design for chaining macros which expand into constant expressions is to wrap the definition in parentheses for 对于许多有用的答案,将宏扩展为常量表达式的最佳链接设计是将定义用括号括起来,

1. Proper order of operations: 1.正确的操作顺序:

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

2. Encourage constant folding by the compiler by grouping constant values together: 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)

The preprocessor just does text substitution. 预处理器只是进行文本替换。 It will evaluate to the second expression with the "extra" multiplies. 它将乘以“ extra”乘积得出第二个表达式。 The compiler will generally try to optimize arithmetic between constants, however, so long as it can do so without changing the answer. 但是,只要编译器可以在不更改答案的情况下进行优化,通常将尝试在常量之间优化算术。

To maximize its chances to optimize, you'll want to be mindful that you keep the constants "next to each other" so that it can see the optimization, especially with floating point types. 为了最大化其优化机会,您需要注意保持常量“彼此相邻”,以便它可以看到优化,尤其是对于浮点类型。 In other words, if t is a variable, you would like 30 * 20 * t instead of 30 * t * 20 . 换句话说,如果t是一个变量,则您希望使用30 * 20 * t而不是30 * t * 20

See the gcc preprocessor macro docs , specifically Object-like Macros . 请参阅gcc预处理程序宏文档 ,尤其是类对象宏

I think the compiler also comes into play here. 我认为编译器也在这里发挥作用。 For example, if we are only considering the preprocessor, then it should expand to 例如,如果我们仅考虑预处理器,则应将其扩展为

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

However, it may be that the compiler (regardless of optimization?) will resolve this to 但是,可能是编译器(不管优化如何?)将其解析为

(t / 5184000L) % 7L)

since these are independent constants and therefore would be faster/simpler code execution. 由于这些都是独立的常量,因此可以更快/更简单地执行代码。

Note1: you should use "(t)" in your definition to protect against unintended expansions/interpretations. 注意1:您应该在定义中使用“(t)”以防止意外的扩展/解释。 Note2: another best practice is to avoid using undef , because this makes the code less readable. 注意2:另一个最佳实践是避免使用undef ,因为这会使代码的可读性降低。 See the notes about how the macro expansion is affected by this (section Object-like Macros ). 请参阅有关此扩展如何影响宏的注释(类对象宏 )。

UPDATE : from the section Object-like Macros : 更新 :从类对象宏部分

When the preprocessor expands a macro name, the macro's expansion replaces the macro invocation, then the expansion is examined for more macros to expand. 当预处理器扩展宏名称时,宏的扩展将替换宏调用,然后检查扩展是否有更多宏要扩展。 For example, 例如,

#define TABLESIZE BUFSIZE #define BUFSIZE 1024 TABLESIZE ==> BUFSIZE ==> 1024 TABLESIZE is expanded first to produce BUFSIZE, then that macro is expanded to produce the final result, 1024. #define TABLESIZE BUFSIZE #define BUFSIZE 1024 TABLESIZE ==> BUFSIZE ==> 1024首先将TABLESIZE扩展为产生BUFSIZE,然后将该宏扩展为产生最终结果1024。

Notice that BUFSIZE was not defined when TABLESIZE was defined. 请注意,定义TABLESIZE时未定义BUFSIZE。 The '#define' for TABLESIZE uses exactly the expansion you specify—in this case, BUFSIZE—and does not check to see whether it too contains macro names. TABLESIZE的“ #define”完全使用您指定的扩展名(在本例中为BUFSIZE),并且不检查其是否也包含宏名。 Only when you use TABLESIZE is the result of its expansion scanned for more macro names. 仅当您使用TABLESIZE时,才会扫描其扩展结果以获取更多的宏名称。

(emphasis mine) (强调我的)

Macro expansion is nothing more than simple text substitution. 宏扩展无非就是简单的文本替换。 After macros have been expanded, the compiler will parse the result and perform its usual optimizations, which should include constant folding. 扩展宏之后,编译器将解析结果并执行其通常的优化,其中应包括常量折叠。

However, this example illustrates a common mistake beginners make when defining macros in C. If the macro is intended to expand to an expression, good C practice dictates that the value should always be wrapped in parentheses if the result would otherwise contain exposed operators. 但是,此示例说明了初学者在C中定义宏时常犯的错误。如果宏打算将其扩展为表达式,则良好的C惯例规定,如果结果否则包含暴露的运算符,则应始终将值包装在括号中。 In this example, look at the definition of TICKS_PER_DAY : 在此示例中,查看TICKS_PER_DAY的定义:

#define TICKS_PER_DAY    TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY

Now look at sec (note that the semicolon should not be present, but I'll ignore that for now): 现在来看sec (请注意,不应出现分号,但我暂时将其忽略):

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

If this is instantiated as sec(x) , it will expand to: 如果将其实例化为sec(x) ,它将扩展为:

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

This is clearly not what was intended. 这显然不是预期的。 It will only divide by the initial 60L , after which the remaining values will be multiplied. 它只会除以初始的60L ,然后将剩余的值相乘。

The correct way to fix this is to fix the definition of TICKS_PER_DAY to properly encapsulate its internal operations: 解决此问题的正确方法是修复TICKS_PER_DAY的定义以正确封装其内部操作:

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

And of course, sec should be an expression macro and should not contain a semicolon, which would prevent it from being used, for instance, in a context like sec(x) + 10 : 当然, sec应该是一个表达式宏,并且不应包含分号,这将阻止它在例如sec(x) + 10

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

Now let's see how sec(x) would be expanded with these bug fixes: 现在,让我们看看如何通过这些错误修复来扩展sec(x)

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

Now this will actually do what was intended. 现在,这实际上可以完成预期的工作。 The compiler should constant-fold the multiplies, resilting in a single divide followed by a single mod. 编译器应将乘数恒定折叠,在一个除法后重新合并一个mod。

Edit: It looks like the missing parentheses have since been added to the original post. 编辑:看起来缺少的括号已被添加到原始帖子。 Without them, it wouldn't work at all. 没有它们,它根本无法工作。 Also the extra semicolon has been removed from the original post. 另外,多余的分号也已从原始帖子中删除。

It expands to: 它扩展为:

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

This is because macros are handled by the pre-processor, which simply expands macros to their values (recursively if necessary). 这是因为宏是由预处理器处理的,预处理器只是将宏扩展为它们的值(必要时递归地)。

BUT this does not mean that the entire computation will be repeated at each point you use sec(t). 但是,这并不意味着在您使用sec(t)的每个点都会重复整个计算。 This is because the computation happens at compile time. 这是因为计算发生在编译时。 So you don't pay the price at run time. 因此,您无需在运行时支付价格。 The compiler pre-computes such constant computations, and uses the computed value in the generated code. 编译器会预先计算此类常量计算,并在生成的代码中使用计算出的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM