Within C programming, there are many commands that start with the # symbol that are used as placeholders for the C preprocessor. These hash-directives can be used to define tokens for substitution, pull in header files, switch code in and out of a build, or determine how a specific value is stored in memory. However, in this blog post, we will focus on macros, which are used to define tokens for substitution and conditionals which are used to switch code in and out of a script.

What is a macro?

A macro encapsulates a series of commands into a single unit, allowing you to automate repetitive tasks. Within C programming, a macro is used to create a name that represents a fragment of code and is defined by using #define. For example,

Within this line of code, we are creating a macro called ARRAY_SIZE and giving it the value of 3. Macros follow the format #define macro_name macro_value. When the name is used, it is replaced with the fragment of code that represents the name. In this case, anytime the name ARRAY_SIZE is called, it will be replaced with the value of 3. Within C programming, the preprocessor checks the code for any defined macros and replaces the macros with the corresponding fragments of code before the compilation process starts. There are two main types of macros: object-like macros and function-based macros.

Object-like macros

Object-like macros create a constant with a defined value.

Output

In this example, two object-type macros are being created: ARRAY_SIZE and MAX_ITEM_LEN, with the values of 3 and 20. These two macro values are the most basic type of macros, as they only have a value assigned to their names. Once the name is assigned a value, the preprocessor will look through the code to change the macros into their assigned values. So whenever you see instances of ARRAY_SIZE being defined, the preprocessor will change that value to 3 before the compilation process starts.

Function-based macros follow the same template as object-like macros but, instead of having a simple value assigned to the name, a function is assigned instead.

Output

In this example, a macro called SQUARE(x) is being defined as the function ((x) * (x)). The SQUARE(x) macro is not holding a simple value like an int or a char, but an entire function that needs an argument passed within the name to perform the function. As we can see in the example, the variable problem is initialized with the value SQUARE(12), which is then replaced by the preprocessor as ((12) * (12)).

Conditionals in C programming

Within C programming, the #ifdef, #else, and #endif are conditionals that look at a block of code and evaluate whether the following block should survive to compilation. The #ifdef conditional looks to see if a certain macro is defined within the code, the #else executes an alternative set of code if the #ifdef fails, and the #endif is used to end the conditional statement. In this example, a struct is created called STRING_EX, and within the structure, we create a set of conditionals to check if the UNICODE macro is defined within the code.

We see that once the conditional is made, the PSTR Buffer portion is grayed out. This means that the #else portion of code will not execute because the UNICODE macro is defined within the code, as we can see here, so the Buffer variable will have the data type PWSTR rather than PSTR. Once the conditional is finished, the #endif simply marks the end of a conditional block started by the #ifdef.

The character set that we are using within the code is the Unicode Character Set, so the UNICODE macro is automatically defined within the code.

Let’s see what happens if the UNICODE macro is not defined within the code.

Instead of using the Unicode character set, the multi-byte character set is going to be used within the code. So the UNICODE macro is not automatically defined within the code.

Now that we look at the code, the PWSTR Buffer under the #ifdef UNICODE is grayed out. Because the UNICODE macro is not defined, the #ifdef portion failed and went straight into the #else portion of code to initialize the Buffer variable to the data type PSTR. Once the #else conditional is finished, then the #endif ends the conditional block.