Home >Backend Development >C++ >#define INC(a) INC(a ?
Does this macro crash GCC? Read and you will have the answer
The goal of this article is to introduce you to the magnificent world of macros in C.
In C, lines that start with a # are interpreted by the compiler when compiling the source files. These are called preprocessor directives. Macros are one of them.
Little historical point:
C language macros were introduced with the first C language standard, called ANSI C (or C89),
which was standardized by the American National Standards Institute (ANSI) in 1989.However, before this standardization, macros were already part of the classic C (or K&R C) language used in the 1970s.
The original C compiler, developed by Dennis Ritchie for the UNIX operating system, already included a rudimentary form of macros via the preprocessor, allowing definitions with #define.
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
The define works quite easily to understand: the compiler replaces all occurrences in the code with the defined value. It works with the following syntax #define
A bit like "Ctrl-f and replace".
We can use defines to define functions that we can use in our code.
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
We can declare macros conditionally.
If a name is already defined then we execute the following piece of code.
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
In this case, I use a #ifndef, but it also exists:
#if (X == 1) #define Y 2 #elif (X == 2) #define Y "Ami de la bonne blague, bonjour !" #else #define Y NULL #endif /* ! X */ /* ... */ int main(void) { #if (X == 1) printf("%d\n", Y); #elif (X == 2) printf("%s\n", Y); #else printf("%p\n", Y); #endif /* ! X */ }
We like to signal the end of #if with a bulk comment. This is a convention that allows you to better navigate the code.
You could see in the previous example that I used the keywords __FUNCTION__ and __LINE__.
As you might expect, these are macros that the compiler will replace with the correct value.
There is a list of Common Predifined macros.
Note that there are so-called System specific macros.
Small non-exhaustive list:
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
Here, we can see that we generate variadic macros, especially useful when creating logs.
(Even if it's not a good idea to make logs with printfs.)
For this, we will have to create an external file, often named *.def although there is no convention.
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
This kind of macro is extremely useful. I must admit that it is rarely found in source code, but it allows you to modify the operation of the program without having to modify the source code. Fun fact, it is often used in the creation of kernels. It allows you to generate global structures such as the IDT and the GDT.
Attention: Quick clarification first, macros are great tools but you have to be careful. You should definitely not use this type of macro:
#if (X == 1) #define Y 2 #elif (X == 2) #define Y "Ami de la bonne blague, bonjour !" #else #define Y NULL #endif /* ! X */ /* ... */ int main(void) { #if (X == 1) printf("%d\n", Y); #elif (X == 2) printf("%s\n", Y); #else printf("%p\n", Y); #endif /* ! X */ }
Let's take an example: MIN(2 5, fibo(25))
MIN(2 5, fibo(25)) => (2 5 < fibo(25) ? 2 5: fibo(25))
Here the problem is the calculation priority. The compiler will first perform the comparison then the addition, therefore 2 (1). We correct this by adding parentheses using the macro arguments.
// Ici, l'opérateur ## est l'opérateur de concaténation #define DEBUG_PRNTF(fmt, ...) printf("LOG" ## fmt, __VA_ARGS__);
As you never know what your users will pass as a parameter, always put parentheses on the arguments.
MIN(2 5, fibo(25)) => (2 5 < fibo(25) ? 2 5: fibo(25))
We notice that the compiler makes a stupid and nasty replacement, which means that we will calculate fibo(25) twice. I'll let you imagine if it's a recursive implementation.
To fix this problem, we declare an intermediate variable before the if.
// color.def X(NC, "\e[0m", "No Color", 0x000000) X(BLACK, "\e[0;30m", "Black", 0x000000) X(GRAY, "\e[1;30m", "Gray", 0x808080) X(RED, "\e[0;31m", "Red", 0xFF0000) X(LIGHT_RED, "\e[1;31m", "Light Red", 0xFF8080) X(GREEN, "\e[0;32m", "Green", 0x00FF00) X(LIGHT_GREEN, "\e[1;32m", "Light Green", 0x80FF80) X(BROWN, "\e[0;33m", "Brown", 0xA52A2A) X(YELLOW, "\e[1;33m", "Yellow", 0xFFFF00) X(BLUE, "\e[0;34m", "Blue", 0x0000FF) X(LIGHT_BLUE, "\e[1;34m", "Light Blue", 0xADD8E6) X(PURPLE, "\e[0;35m", "Purple", 0x800080) X(LIGHT_PURPLE, "\e[1;35m", "Light Purple", 0xEE82EE) X(CYAN, "\e[0;36m", "Cyan", 0x00FFFF) X(LIGHT_CYAN, "\e[1;36m", "Light Cyan", 0xE0FFFF) X(LIGHT_GRAY, "\e[0;37m", "Light Gray", 0xD3D3D3) X(WHITE, "\e[1;37m", "White", 0xFFFFFF)
Here, it's purely overkill code just for fun. I don't necessarily advise you to use these macros in your code.
I'm just having fun (a good thing in life).
typedef struct { const char *name; const char *ansi_code; const char *description; unsigned int rgb; } Color; #define X(NAME, ANSI, DESC, RGB) { #NAME, ANSI, DESC, RGB }, Color colors[] = { #include "color.def" }; #undef X #define X(NAME, ANSI, DESC, RGB) printf("%s (%s) = %s\n", #NAME, DESC, #RGB); void print_colors() { // Bien entendu, on pourrait itérer sur la structure créée mais c'est une illustration #include "color.def" } #undef X
I'll let you test with a little -fsanitize=address. It's really crazy. We could even see an improvement to the auto_free function which takes as a parameter a character string of the name of our structure to make a switch.
More chill function where we just calculate the execution time of our function. Very useful for benchmarking.
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
Small X-macro which takes a macro as an argument and expands it.
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
Here, we actually generate entire functions with a macro, because C has no limits. Me too?
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
Now it’s time to wrap things up. We saw lots of really cool things. And if you are ever tempted, you are free to discover the macros for yourself. There are still plenty of things to see.
So, conclusion: RTFM.
PS: As for the title, macros are not recursive, they only expand with a depth of 1 and in our present case, GCC will make an implicit_declaration on INC and crash.
The above is the detailed content of #define INC(a) INC(a ?. For more information, please follow other related articles on the PHP Chinese website!