ProgrammingSystem C/C++ Developer, Embedded Engineer

How and why to use preprocessor macros (#define, #ifdef, #ifndef, #include) in C language?

Pass interviews with Hintsage AI assistant

Answer.

Preprocessor macros in C were introduced as part of the language to ensure portability, readability, and convenience in configuring source code. With directives such as #define, #ifdef, and #ifndef, you can create conditional code sections, declare constants, and include external files (#include).

Background:

Preprocessor directives were introduced to simplify the adaptation of source code to different systems and compilers, as well as to automate repetitive tasks.

Problem:

Without a preprocessor, it was impossible to abstract from platform differences, repeat code fragments, or protect against multiple inclusions of header files. Furthermore, care must be taken as macros are not typed and do not have a scope.

Solution:

Using preprocessor macros to declare constants, inline functions, conditional compilation, and to prevent multiple inclusions of header files.

Code example:

#ifndef MY_HEADER_H #define MY_HEADER_H #define MAX_SIZE 100 #ifdef DEBUG #define LOG(x) printf("%s\n", x) #else #define LOG(x) #endif #endif /* MY_HEADER_H */

Key features:

  • Macros are not subject to type checking by the compiler
  • #ifdef/#ifndef allow for creating portable and parameterized code
  • #include is protected against multiple inclusions using include guards

Trick questions.

What happens if you forget the include guard in a header file?

The header file may be included in one .c file multiple times (implicitly through other .h files), leading to redefinition errors.

What is the difference between a #define macro and an inline function declaration?

#define simply replaces text without checking types and syntax correctness, while an inline function is a regular function that is checked by the compiler during type analysis.

Can macros have side effects?

Yes, if a macro is defined carelessly, the passed expressions may be evaluated multiple times. For example:

#define SQUARE(x) (x) * (x) int a = SQUARE(++i); // ++i will execute TWICE!

Common mistakes and anti-patterns

  • Macros with side effects and ambiguous logic
  • Lack of protective directives (include guards) in header files
  • Using macros for complex operations instead of functions

Real-life example

Negative case

Using a macro without parentheses and with an expression that creates side effects:

#define DOUBLE(x) x + x int result = DOUBLE(1+2); // expected result is not 6, but 1+2+1+2=6? No, it evaluates to 1+2+1+2. // In reality, it will be 1 + 2 + 1 + 2 = 6, but if DOUBLE(++i), then ++i will apply twice.

Pros:

  • Concise notation

Cons:

  • Errors due to incorrect evaluation order, side effects

Positive case

Defining a macro with parentheses and using it for simple constants:

#define DOUBLE(x) ((x) + (x)) #define BUFFER_SIZE 1024

Pros:

  • Safety, absence of side effects
  • Clear structure of code

Cons:

  • Need to remember the syntax of macros and exercise caution