Master the ‘Black Magic’ of C Language Macros in 5 Minutes! `#` and `##` Double Your Code Efficiency

Are you still using the most basic<span>#define PI 3.14</span>?80% of embedded engineers have not truly harnessed the power of macros! When you see meaningless entries in your debug logs like<span>val=123</span>, when your code is filled with repetitive type conversions, and when you want to implement generic operations but feel helpless—today, these two symbols willrevolutionize your understanding of C language macros!

1. Dissecting Macro Operators:<span>#</span> and <span>##</span>—who are they?

1. Stringification Operator <span>#</span>

#define LOG(x) printf(#x " = %d\n", x)
int temp = 42;LOG(temp); // Output: temp = 42

Effect Comparison:

  • Original Debug:<span>printf("val=%d", val);</span> → Requires manual entry of variable name

  • Advanced Solution:<span>LOG(val);</span>Automatically captures variable name

    Master the 'Black Magic' of C Language Macros in 5 Minutes! `#` and `##` Double Your Code Efficiency

2. Concatenation Operator <span>##</span>

Achievescompile-time code concatenation, the core of embedded generic programming:

#define DECLARE_SETTER(type) \
    void set_##type(type##_t* ptr, type value) { *ptr = value; }
DECLARE_SETTER(int);   // Generates set_int(int_t* ptr, int value)
DECLARE_SETTER(float); // Generates set_float(float_t* ptr, float value)

Three Revolutionary Values:

  1. Eliminate Duplicate Code: Automatically generate functions for different data types

  2. Zero Runtime Overhead: All work is done at the preprocessing stage

  3. Unified Interface: Standardize device driver layer API naming

2. Embedded Practice: You Miss Out If You Don’t Use These Scenarios

1. Device Register Operations (A Must-Read for Hardware Engineers)

Pain Points of Traditional Writing:

// Each register requires similar code to be written
void set_GPIOA_ODR(uint32_t val) { *((volatile uint32_t*)0x40020014) = val; }
void set_GPIOB_ODR(uint32_t val) { ... } // Repetitive labor!

##Solution:

#define REG_SETTER(periph) \
    void set_##periph##_ODR(uint32_t val) { \
        *((volatile uint32_t*)(periph##_BASE + ODR_OFFSET)) = val; \
    }

REG_SETTER(GPIOA); // Automatically generates GPIOA setting function
REG_SETTER(GPIOB); // Automatically generates GPIOB setting function

2. Multi-level Debug Log System (Locate Bugs 10 Times Faster)

# and ## Combination Technique:

#define logd(fmt, ...) \
do { \
if (LOG_FILTER_LEVEL <= LOG_LEVEL_DEBUG) { \
uart_print(&huart1, "|DEBUG| %08d | %s.%s:%d | " fmt "\r\n", \
__LOG_SEC__, __FILE__, __func__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
LOG( "AT Command Param \r\n");      // No parameter version
LOG("ADC overflow: %d", adc_val); // Parameter version

Output Effect:

Master the 'Black Magic' of C Language Macros in 5 Minutes! `#` and `##` Double Your Code Efficiency

3. Hard Lessons: I’ve Stepped on These Pits for You

Three Major Taboo of Macro Operators (Saved My Project)

1. Avoid Nesting in Complex Expressions:

// Error! The compiler may not be able to resolve int result = 10 + CONCAT(var, 1); 

Correct Operation:

#define CONCAT(a,b) a##b
int var1 = 5;
int tmp = CONCAT(var, 1); // Assign to intermediate variable first
int result = 10 + tmp;

2. Parameter Expansion Order Trap:

#define SQUARE(x) x*x
int a = 2;
int bad = SQUARE(a+1); // Expands to a+1*a+1 = 2+1*2+1=5 (Expected 9!)

Rescue Plan:All parameters and results must be enclosed in parentheses!

#define SAFE_SQUARE(x) ((x)*(x))

3. Avoid Using<span>++</span>/<span>--</span> in Macros:

#define MAX(a,b) ((a)>(b)?(a):(b))
int x=1, y=2;
int z = MAX(x++, y++); // After expansion, x is incremented twice!

4. Advanced Techniques: ‘Black Technology’ That the Compiler Can’t Understand

1. Dynamically Creating Enum Types (Protocol Parsing Artifact)

#define ADD_CMD(name, id) CMD_##name = id,
typedef enum  {
        ADD_CMD(READ,  0x01)
        ADD_CMD(WRITE, 0x02)
        ADD_CMD(ERASE, 0x03)} CommandType;
// Generates: enum { CMD_READ=0x01, CMD_WRITE=0x02, CMD_ERASE=0x03 };

2. Automatic Registration of Function Pointer Tables (Driver Layer Architecture Optimization)

#define REGISTER_DRIVER(name) \
    { #name, name##_init, name##_read }
struct Driver {
        const char* name;
        int (*init)(void);
        int (*read)(uint8_t*);
};
struct Driver drivers[] = {
    REGISTER_DRIVER(ADC),
    REGISTER_DRIVER(SPI),
    REGISTER_DRIVER(I2C)};// Automatically fills: {"ADC", adc_init, adc_read}...

Conclusion: The Ultimate Philosophy of Macro Operators

“Macros are not functions, but code generators”

When you can use<span>#</span> and <span>##</span> to achieve:✅ 20 lines of code to accomplish what used to take 200 lines✅ Changing data types requires only one definition change✅ Debug logs come with contextual semantics—you have trulymastered the metaprogramming capabilities of C language!

Discussion: What pitfalls have you encountered while using macro operators? Or do you have any stunning application scenarios?Feel free to share your “bloody history” or “magic operations” in the comments below!If you find this article has resolved years of confusion, please like 👍, share with your teammates, and save 📁 for future reference!

Original Declaration: This article is originally published by [Embedded Xiao Jin], and sharing is welcome. For reprinting, please contact for authorization.

Author: Xiao Jin

Please open in the WeChat client

Leave a Comment