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). 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. 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)
:
(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?
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:
(t / 60 * 60 * 60 * 24) != (t / (60 * 60 * 60 * 24))
2. Encourage constant folding by the compiler by grouping constant values together:
// 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 macro in C language is known as the piece of code which can be replaced by the macro value. The macro is defined with the help of #define preprocessor directive and the macro doesn't end with a semicolon(;). Macro is just a name given to certain values or expressions it doesn't point to any memory location.
You can't. Macros are expanded by the Preprocessor, which happens even before the code is compiled. It is a purely textual replacement.
The LENGTH and BREADTH are called the macro templates. The values 10 and 20 are called macro expansions. When the program run and if the C preprocessor sees an instance of a macro within the program code, it will do the macro expansion. It replaces the macro template with the value of macro expansion.
The process to redefine a Macro is: Macro must be defined. When, you want to redefine the Macro, first of all, undefined the Macro by using #undef preprocessor directive. And, then define the Macro again by using #define preprocessor directive.
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. In this example, look at the definition of 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):
#define sec(t)((t / TICKS_PER_DAY) % DAYS_PER_WEEK);
If this is instantiated as sec(x)
, it will expand to:
((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.
The correct way to fix this is to fix the definition of TICKS_PER_DAY
to properly encapsulate its internal operations:
#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
:
#define sec(t) ((t / TICKS_PER_DAY) % DAYS_PER_WEEK)
Now let's see how sec(x)
would be expanded with these bug fixes:
((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.
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.
The preprocessor just does text substitution. It will evaluate to the second expression with the "extra" multiplies. 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
.
See the gcc preprocessor macro docs, specifically Object-like Macros.
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.
Note2: another best practice is to avoid using undef
, because this makes the code less readable. 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.Notice that BUFSIZE was not defined when TABLESIZE was defined. 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. Only when you use TABLESIZE is the result of its expansion scanned for more macro names.
(emphasis mine)
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). 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With