About Me
Welcome to my blog! This is where I collect my observations and notes on programming and technology. The main subjects range from implementation details to broader ideas about programming.
Main Topics
- Engineering Projects: Exploring implementation details and how technical systems work.
- C/C++: Notes on language features and programming techniques.
- The Programmer’s Perspective: Ideas about developing a career and a way of thinking as a programmer.
For more, visit the categories page.
Contact
If you have questions or would like to discuss something, please get in touch through the About page.
Thank you for reading and for your support. I hope these notes help you on your own technical journey!
Using a Macro to Check Whether Another Macro Is Defined
The original question is on Stack Overflow:
https://stackoverflow.com/questions/26099745/test-if-preprocessor-symbol-is-defined-inside-macro
Consider this definition:
1 | #define TRACE(x) "" #x |
#x uses the C preprocessor’s stringizing operator to turn the argument’s tokens into a string literal. For example:
1 | printf(TRACE(this_macro)); |
This prints this_macro.
Why Put an Empty String Before #x?
Compare these two definitions:
1 | #define TRACE2(x) #x |
I asked several AI systems what difference the two forms made. Their common claim was that the results were usually the same but differed in a few unusual cases.
Claude claimed that TRACE3(x) was safer because it concatenated multiple macro arguments into one string, whereas TRACE2(x) did not. Gemini 1.5 Pro offered this example:
1 | printf(TRACE3(hello) TRACE3(world)); |
It claimed that TRACE3 could join the strings while TRACE2 could not. GPT-4 suggested that TRACE3 would still produce an empty string for an invalid argument and was therefore safer.
I initially accepted these explanations, but wrote macro1.c to test them:
1 |
|
None of the ordinary cases showed a difference. I then tried an empty argument. Following the AI explanations, I expected TRACE2 to produce no string and fail, while TRACE3 would produce an empty string. Instead, both produced the same warning:
1 | macro1.c: In function ‘main’: |
To check whether optimization caused the result, I disabled it with CFLAGS=-O0:
1 | gcc macro1.c CFLAGS=-O0 |
The result remained the same. At the time, I had no satisfactory explanation and moved on to the macro’s more interesting use.
The important distinction is that these were claims I tested, not conclusions established by the experiment. Stringizing an empty macro argument already produces an empty string literal; prepending "" is not what makes that case valid. Adjacent string literals are concatenated in either form when they are present.
Testing Whether a Macro Expands
The first answer in the linked discussion defines:
1 |
|
If a macro is defined to a replacement that changes its spelling, the extra TRACE_STRINGIFY expansion stage expands it before stringizing it. In "" #macro, direct stringizing instead preserves the argument’s original spelling.
The resulting strings differ, so strcmp returns a nonzero value and the print statement runs. If the identifier does not expand, both paths produce the same string. strcmp returns zero and nothing is printed.
This explanation also reveals a limitation: the test does not recognize a self-referential definition such as:
1 |
The example above is C++. I adapted it to C in macro2.c.
Adding a Diagnostic
Here is macro2.c:
1 |
|
I changed TRACE to report the case where the macro does not expand. The logical change is simply negating the result of strcmp.
A variadic argument list supplies the diagnostic message to fprintf, which writes to stderr. The predefined macros __FILE__ and __LINE__ identify the call site.
This can be useful for reporting an expected macro that is missing in a project without a configuration system. The original notes also considered how such checks relate to configuration-driven builds using tools such as Kconfig. However, this string-comparison technique is a runtime expansion test, with the limitation described above; it does not replace #if or #ifdef when the preprocessor must actually include or exclude source code.
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !