Macros in C – Everything You Need to Know

Updated on October 28, 2024

Article Outline

Macros are an important element in the C programming language. They allow the programmer to define some code that can be reused and replaced by the preprocessor in the program before compilation. Macros are a significant means of strengthening the flexibility and elegance of code, making it easy to represent complex expressions, and optimising performance. Macros have multiple types, and various predefined macros in C are also available that users can utilise directly in their code.

 

In this article, we will learn about macros in C, from basic definitions to implementation, types with their syntaxes, examples, and the common pitfalls to avoid.

What are Macros in C?

Macros are pieces of code that replace them with their values. They are defined using the #define directive and do not end with a semicolon. Unlike a variable, a macro does not represent some memory location; rather, it represents some numeric constant or expression. This replacement is done before actual compilation, so macros affect code generation but do not affect runtime behavior.

 

Macros in C offer a powerful way to simplify and reuse code. Using more macros reduces the possibility of errors and makes code easier to read and maintain. Macros are a blessing to any programmer because they save time and help avoid the necessity of writing the same code multiple times. They are used to define constants or for the creation of inline functions.

Syntax of Macros in C

The general syntax for defining a macro is:

 

#define macro_name macro_value

 

Here,

  • #define: Preprocessor directive used to define a macro.
  • macro_name: Name of the macro that will be replaced in the code.
  • macro_value: The value or code that will replace macro_name wherever it is used in the program.
*Image
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure

Why do we use Macros in C?

Programs in C can be made more efficient by using macros. The code is easier to develop, read, and maintain when macros are used. Here’s why it is important to use macros in C:

 

  1. Code Reusability: Macros reduce redundancy in the program by including defined chunks of code that can be reused.
  2. Performance: When macros are used in a C program, they get resolved in the preprocessing stage and thus do not bear any costs at runtime.
  3. Ease of Updates: If you use macros to define constants or expressions, you need to make changes in the macro definition only, and all occurrences in the code will automatically change to reflect it.
  4. Conditional Compilation: Macros are defined for condition-based code inclusion and exclusion. It is used to write cross-platform code, for debugging and compiling different versions of the program.
  5. Code Abstraction: The use of macros enables shortening the code by allowing the use of a defined name for complex operations, thus making it more abstract and easily readable.

What are Preprocessor and Preprocessor Directives in C?

In the C programming language, preprocessors and preprocessor directives are essential tools that alter code before compilation. Before the compilation, the source code is preprocessed by the preprocessor.

 

Here’s how the preprocessor works and checks whether there are any preprocessor directives to be performed, and if not then it moves for the compilation process.

Preprocessor Directives in C

What is a Preprocessor?

 

The preprocessor is a program run before the compiler processes the source code. It typically handles macro expansion, file inclusion, and conditional compilation. Since no semantic check has been done yet, it is often run under the assumption that no rules of the language have been violated. In C, the preprocessor is invoked on a program before the actual compilation begins, by processing all lines starting with a # that exists in the code.

 

Key Operations of the Preprocessor Include:

  1. Macro Expansion: Replacing macros with their defined values or code.
  2. File Inclusion: It includes the header files into the source code with the help of the #include directive.
  3. Conditional Compilation: The ability to compile code based on certain conditions set by preprocessor directives such as #ifdef and #ifndef.

 

What are Preprocessor Directives?

 

Preprocessor directives are lines in a program that begin with # and give instructions to the preprocessor; they do not end with a semicolon. Preprocessor directives are processed before the code is compiled. Below are some common preprocessor directives used in C:

  1. #define: Defines a macro.
  2. #include: Includes a header file in the program.
  3. #undef: Undefines a macro.
  4. #ifdef / #ifndef: Conditional compilation based on whether a macro is defined or not.
  5. #if / #elif / #else / #endif: Conditional compilation based on a specific condition.

 

Example:

#define PI 3.14159 // #define directive #define SQUARE(x) ((x) * (x)) #include <stdio.h> // #include directive int main() { printf("Value of PI: %fn", PI); // prints the PI macro value printf("Square of 4: %dn", SQUARE(6)); // prints the SQUARE macro value return 0; }

Output:

Value of PI: 3.141590 Square of 4: 16

Types of Macros

Macros in C language are mainly of two types but there is another macros which is not commonly used like the other two. We will now see each of them in detail one by one.

Object-like Macros

An object-like macro is a token that will be replaced by a constant value or expression.  In other words, object-like macros get replaced by certain values or segments of code. Typically, object-like macros are used to define constants or simple expressions that do not require arguments.

 

Also Read: Compilation Process in C

 

Syntax:

#define macro_name macro_value

Example:

#include <stdio.h> // defining an object-like macros #define PI 3.141 int main() { // prints the PI macros value printf("The value of PI is: %fn", PI); return 0; }

Output:

The value of PI is: 3.141

Function-like Macros

A function-like macro works like a function. However, the difference in functionality is that a function is called at runtime, whereas a macro is replaced at compile time. This helps you avoid the overhead of going to the function by replacing the code directly.

 

Syntax:

#define macro_func_name(params) ((params) * (params))

Example:

#include <stdio.h> // defining a function-like macros #define SQUARE(x) ((x) * (x)) int main() { // inputs the number to find the square int num = 6; // prints the square using the SQUARE macros printf("Square value of %d is: %dn", num, SQUARE(num)); return 0; }

Output:

Square value of 6 is: 36

Chain-like Macros

Chain-like macros are macros inside other macros. It is a more flexible and modular method of constructing constants, functions, or expressions made possible by the ability to define chain-like macros so that they depend on other macros. You can design complex expressions or features that are simple to modify and maintain by chaining macros together.

 

Syntax:

#define macro_func_name(params) ((params) * (params))

Example:

#include <stdio.h> // Define basic macros #define PI 3.14159 #define SQUARE(x) ((x) * (x)) // Chain-like macro to calculate the area of a circle #define AREA_CIRCLE(r) (PI * SQUARE(r)) // Chain-like macro to calculate the circumference of a circle #define CIRCUMFERENCE_CIRCLE(r) (2 * PI * (r)) int main() { double r = 3.5; // Using chained macros to calculate area and circumference double area = AREA_CIRCLE(r); double circum = CIRCUMFERENCE_CIRCLE(r); // Print the results printf("The area and circumference of a circle with a radius of %.2f:n", r); printf("Area: %.2fn", area); printf("Circumference: %.2fn", circum); return 0; }

Output:

The area and circumference of a circle with a radius of 3.50: Area: 38.48 Circumference: 21.99

Predefined Macros in C Language

The C programming language offers some pre-defined macros for extracting useful information about the current program’s state or environment. All of these macros are set by the compiler itself automatically, available throughout the code. Below are the most commonly used predefined macros:

 

Macro Name Description
__DATE__ It contains the current date in the format “MMM DD YYYY” (e.g., “Oct 23 2024”).
__TIME__ It contains the current time in the format “HH:MM”.
__FILE__ It provides the name of the current file being compiled.
__LINE__ It provides the current line number in the source code.
__STDC__ It is defined as 1 when the program conforms to the ANSI standard.
__cplusplus It is defined when the code is being compiled as C++ code.
__func__ It defines the name of the current function.
__STDC_VERSION__ It defines the constant indicating the version of the C Standard like C90, etc.
__STDC_HOSTED__ It is defined as 1 if the compiler’s implementation is hosted.

 

Let’s now see some examples of implementing these predefined macros in C:

 

Also Read: Features of C Language

 

Example 1: Using __DATE__, __TIME__, __FILE__, and __LINE__ macros in C.

#include <stdio.h> int main() { // __DATE__ expands to the current date printf("Compilation Date: %sn", __DATE__); // __TIME__ expands to the current time printf("Compilation Time: %sn", __TIME__); // __FILE__ expands to the name of the current file printf("Source File Name: %sn", __FILE__); // __LINE__ expands to the current line number in the source code printf("This line is located at line number: %dn", __LINE__); // A simple example to demonstrate the change in line number printf("This is line number: %dn", __LINE__); return 0; }

Output:

Compilation Date: Oct 23 2024 Compilation Time: 15:19:34 Source File Name: /tmp/uIFt8EpJbV.c This line is located at line number: 14 This is line number: 17

In this example, we demonstrate the usage of several predefined macros such as __DATE__, __TIME__, __FILE__, and __LINE__, that are typically useful for debugging, logging, or tracking when and where the code was compiled.

 

Example 2: Using __STDC__, __STDC_VERSION__, __cplusplus, and __func__ macros in C.

#include <stdio.h> // Function to display information about the current environment and function name. void showInfo() { // __func__ expands to the name of the current function. printf("This is the current function: %sn", __func__); } int main() { // __STDC__ is defined as 1 #ifdef __STDC__ printf("ANSI C Standard is supported.n"); #else printf("ANSI C Standard is not supported.n"); #endif // __STDC_VERSION__ expands to the C standard version being used. #ifdef __STDC_VERSION__ printf("C Standard Version: %ldn", __STDC_VERSION__); #else printf("C Standard Version is not defined.n"); #endif // __cplusplus is defined if compiling in C++ mode. #ifdef __cplusplus printf("This code is compiled in C++ mode.n"); #else printf("This code is compiled in C mode.n"); #endif // Calling the function showInfo(); return 0; }

Output:

ANSI C Standard is supported. C Standard Version: 201710 This code is compiled in C mode. This is the current function: showInfo

In this example, we have used more advanced predefined macros that provide details about the C standard, compilation mode, and function names have been used in this example. These macros contribute to the code’s increased flexibility, portability, etc.

Common Uses of Macros

Macros in C are a powerful technique for code abstraction and optimization. They enable the definition of reusable snippets of code, the simplification of complex expressions, and the conditional inclusion of code into a program. These are the most frequent applications of macros which are as follows:

Constants

One of the widespread uses of macros is to define constants in a program. By using a macro for a constant, instead of hard-coded values scattered all over the code, you have a clearer and readable code. Constants help in code maintainability.

 

Example:

#include <stdio.h> // Define a constant macro for PI #define PI 3.14159 int main() { float r = 6.0; float area = PI * r * r; // Use of the constant macro printf("Area of the circle: %.2fn", area); return 0; }

Output:

Area of the circle: 36.0

In this example,

  • The value of macro PI is defined as 3.14159. By doing this, the value of PI no longer needs to be manually typed each time it is used.
  • The program uses PI to determine a circle’s area. This makes the code easier to read and maintain.

Inline Functions

Macros may serve as inline functions through simple computing or operations which help the programmer to avoid the cost of calling a function for small commonly used operations.

 

Example:

#include <stdio.h> // Define a macro to calculate the square of the num #define SQUARE(x) ((x) * (x)) int main() { int num = 5; printf("The square of %d is %dn", num, SQUARE(num)); return 0; }

Output:

The square of 5 is 25

In this example,

  • The value of macro SQUARE(x) takes an argument x and returns its square. It’s used just like a function but avoids the overhead of a function call.
  • This is especially useful for small, frequently used operations where performance is critical.

 

Also Read: Functions in C Programming

Conditional Compilation

Macros are widely used for conditional compilation. This is compiling code portions when only certain conditions are met which is especially relevant for cross-platform or configuration-dependent code bases.

 

Example:

#include <stdio.h> // Define DEBUG macro for conditional compilation #define DEBUG 1 int main() { int x = 5; #ifdef DEBUG printf("Debugging: x = %dn", x); // This will only compile if DEBUG is defined #endif return 0; }

Output:

Debugging: x = 5

In this example,

  • The #ifdef directive is used which checks whether the DEBUG macro is defined. If it’s defined, the debugging information is printed.
  • Here, the value of x is printed as 5.

Advanced Macro Concepts

Macro Arguments

Macros, like functions, can have arguments. This means that code can be written with a higher level of abstraction and has higher reusability. However, macros cannot be type-safe as functions can as they are sequentially substituted to code.

 

Example:

#include <stdio.h> // Define a macro with arguments to calculate the maximum of two numbers #define MAX(a, b) ((a) > (b) ? (a) : (b)) int main() { //Define the values of a and b int a = 10, b = 20; printf("The maximum between %d and %d is %dn", a, b, MAX(a, b)); return 0; }

Output:

The maximum between 10 and 20 is 20

In this example,

  • The ternary operator is used by the MAX(a, b) macro to yield the maximum of two values. This is a typical scenario where macros with arguments are used to streamline repetitive operations or comparisons.

Stringizing Operator (#)

In macros, there is a stringizing operator (#) which enables converting the arguments of the macro to string literals. This is useful when you need to create strings from macro arguments.

 

Example:

#include <stdio.h> // Stringize the argument passed to the macro #define TO_STRING(x) #x int main() { // Print the macro as a string printf("Macro argument as string: %sn", TO_STRING(Hello HeroVide)); return 0; }

Output:

Macro argument as a string: Hello HeroVide

In this example, the TO_STRING(x) macro converts the argument Hello HeroVide into a string literal “Hello HeroVide”. The # operator is used for stringizing the macro argument.

Token Pasting Operator (##)

In macros, two tokens are concatenated using the token-pasting operator (##). This helps generate code dynamically by mixing parameters or creating new identifiers.

 

Example:

#include <stdio.h> // Token-pasting macro to combine two tokens #define CONCAT(a, b) a##b int main() { int xy = 100; printf("Value of xy: %dn", CONCAT(x, y)); // Expands to xy return 0; }

Output:

Value of xy: 100

In this example, the ## operator is used by the CONCAT(a, b) macro to concatenate a and b. CONCAT(x, y) extends to xy, an existing variable, in the main function.

Common Pitfalls and Issues with Macros in C

Although macros offer the power of flexibility, if they are not utilised wisely, they can have some drawbacks. Here are the common pitfalls and issues with macros in C:

 

  • Lack of type safety

Macros lack type safety as they don’t perform type checking, unlike functions. With this, if a user tries to pass incompatible types of parameters, it may lead to unexpected behaviour.

 

Example:

#define SQUARE(x) ((x) * (x)) int main() { printf("Square of 5.5: %dn", SQUARE(5.5)); // Undefined behaviour due to type mismatch return 0; }

In this example, passing a float to the SQUARE macro causes improper behaviour because it does not enforce types. However, functions prevent such problems by enforcing type safety.

 

  • Multiple Evaluations

Macros may assess their arguments more than once, which could result in inefficiency or unexpected outcomes. This is a common issue when working with macros in C.

 

Example:

#define SQUARE(x) ((x) * (x)) int main() { int i = 3; printf("Square of i++: %dn", SQUARE(i++)); // Multiple evaluations of i return 0; }

When the macro SQUARE(i++) expands to ((i++) * (i++)), i is doubled up, which has unexpected consequences. While functions only evaluate their arguments once, macros frequently have this problem.

 

  • Precedence Problems

Macros may also result in issues with operator precedence. Therefore, programmers must be careful while implementing operator precedence in C.

 

  • Complex Debugging

Debugging macro-related problems might be challenging since macros are substituted by their definitions during preprocessing. And because the debugger doesn’t step through macros, it can be challenging to identify the issue’s origin.

 

  • Parentheses Misuse

Not placing parentheses around macro arguments can lead to unexpected behaviour due to operator precedence.

 

Example:

#define ADD(x, y) x + y int main() { printf("Result: %dn", 5 * ADD(2, 3)); // Wrong result due to lack of parentheses return 0; }
  • Misuse of Conditional Macros

Conditional logic in macros can lead to unexpected behaviour and confusion, particularly if the logic is multi-line. This can be resolved by properly commenting and formatting multi-line macros to improve readability.

 

Also Read: C Programming Interview Questions and Answers

Best Practices for Using Macros

While working with macros in C, it may lead to hard-to-debug errors, inefficiencies, and unintended behaviour. Therefore, it is crucial to follow the best practices to minimise the risks and ensure macros serve their purpose.

 

1. Use Parentheses Liberally in Macro Definitions

Macros as the means of extending the C language are restricted in every direction. First, the braces should be used whenever arguments are invoked. The more complicated and tangled the macro is, the greater the importance of using braces. It can and will work without it, however, the chance of a variable being declared might (most likely) occur.

 

Example:

#define ADD(x, y) ((x) + (y))

 

2. Avoid Side Effects in Macro Arguments

Over-expansion of macro arguments can become an unexpected problem as well—such as if the argument represents a function call or an increment operator. To avoid this mess, one must ensure that the arguments issued for the macro are not valid expressions.

 

3. Use Macros for Constants, Not Functions

The general recommendation is to use functions over macros unless it is required to create a constant. It is harmonised in a more elegant manner increasing the maintainability of the code. Alongside this, the functions will free programmers from constraints such as inline or macro overhead.

 

Example:

int max(int a, int b) { return (a > b) ? a : b; }

4. Use do-while (0) for multi-statement macros.

Macros that contain multiple statements can behave unpredictably if not properly enclosed within a block. To ensure they behave like a single statement, wrap them in a do-while (0) block.

 

Example:

#define PRINT_VALUES(x, y) do { printf("%dn", x); printf("%dn", y); } while (0) if (condition) PRINT_VALUES(70, 60);

5. Use const Instead of #define for constants.

C programmers inherently utilise many macros. As an alternative, it is better to use const variables instead of using Macros. Most of the time const variables are type variables which bring out better visibility of the newly defined constant whereas macros do not have any type and can be replaced by inverted variables.

 

Example:

const int max_size = 1000;  // Type-safe constant

 

6. Use Inline Functions Instead of Function-Like macros.

In such scenarios, the use of inline functions is preferred as they are memory efficient. Inline does let programmers write macros but brings opportunities to improve scoping, defining better arguments as well ensuring the arguments only return sensible results.

 

Example:

inline int square(int x) { return x * x; }

7. Limit the Scope of Macros

Limit the scope of macros whenever possible by containing them in a single file or by using #undef to undefine them after they have been used.

 

Example:

#define TEMP_BUFFER_SIZE 256 // Use the macro in this file only #undef TEMP_BUFFER_SIZE

Conclusion

Macros in C are the best application to use for increasing the program efficiency. Their proper application is likely to improve the performance and make it easy to implement the code. Improper use, however, causes several issues, mostly of a complex nature, and is most definitely a maintenance issue. Learning the syntax, following usage examples, and being alert to the common pitfalls of macros are important for writing efficient and less error-prone c programs. It increases the chances of the code being more clean, less repetitive, and much easier to maintain. We have covered everything thoroughly regarding macros in C including types such as object-like, function-like, and chain-like macros. We also saw how they can be misused in C and what measures to take to avoid these problems.

 

Macros have effective uses in enhancing the clarity of code, reducing the volume of repetitive tasks and other performance. All that said, you should always pay attention to clean code practices, such as the conservative use of parentheses and side effects. Planning to kickstart your coding career? Consider pursuing Hero Vired’s Certificate Program in DevOps & Cloud Engineering, which is offered in collaboration with Microsoft.

FAQs
A macro in C is a code block that has been assigned a name. It is described by the use of the #define preprocessor directive and each time the name appears in code, it is substituted by the preprocessor with code or a value that this macro represents. Macros are implemented by the C preprocessor and placed before the compilation process, and they can be applied to various entities like constants, functions, and others.   Example: #define PI 3.141  // Macro defining a object-like #define SQUARE(x) ((x) * (x))  // Macro defining a function-like
In C, there are mainly three types of macros including object-like, function-like, and chain-like macros. Object-like macros represent constants or simple expressions and do not take arguments. Function-like macros resemble function calls and can take arguments. Chain-like macros are nothing but macros inside another macros.
Macro functions and regular functions differ in several key ways:   Macro Functions:
  • The macros are processed by the preprocessor, meaning the code is inserted without expanding at compilation.
  • Macros also have the drawback of no type checking and as such they may cause some errors when incorrectly applied.
  • Macros can cause some unnecessary multiple evaluations of the same expression for some reason, and this may lead to side effects.
  • They are normally faster because there is no overhead included with a function call but rather they expand the code since additional copies are involved.
  Regular Functions:
  • Parameters are type-checked, so they are, and functions are compiled and called during runtime.
  • Functions provide more readability, more maintainable code, and easier debugging.
  • Structured programming language functions prevent unnecessary recalculation of expressions and their side effects.
  • Function calls are slightly expensive as compared to macros.
Predefined macros in C are macros that are automatically available in every C program. The details that are included in the C program may be the source file name, date and time of compilation, and other useful attributes. They are built in the compiler and the user cannot change how they are designed.   Some of the common predefined macros in C include __DATE__, __TIME__, __FILE__, __LINE__, etc.
## is a special symbol in C macro and is a token-pasting operator which is used to combine token groups during macros. It combines two tokens into one token so that the two will not exceed this operator. It is utilised in the advanced macro programming to merge tokens on demand, this kind of usage is less seen.   Example: #define CONCAT(p, q) p##q int CONCAT(var, 5) = 10;  // This will create a variable named var5

Updated on October 28, 2024

Link
left dot patternright dot pattern

Programs tailored for your success

Popular

Management

Data Science

Finance

Technology

Future Tech

Upskill with expert articles

View all
Hero Vired logo
Hero Vired is a leading LearnTech company dedicated to offering cutting-edge programs in collaboration with top-tier global institutions. As part of the esteemed Hero Group, we are committed to revolutionizing the skill development landscape in India. Our programs, delivered by industry experts, are designed to empower professionals and students with the skills they need to thrive in today’s competitive job market.
Blogs
Reviews
Events
In the News
About Us
Contact us
Learning Hub
18003093939     ·     hello@herovired.com     ·    Whatsapp
Privacy policy and Terms of use

|

Sitemap

© 2024 Hero Vired. All rights reserved