Home  >  Article  >  Backend Development  >  #define INC(a) INC(a ?

#define INC(a) INC(a ?

Susan Sarandon
Susan SarandonOriginal
2024-10-22 06:10:31478browse

#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.

A preprocessor directive

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

#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 . It is a convention to put the name in capital letters, the value is optional.

A bit like "Ctrl-f and replace".

Mama, the macro

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;

If or not if

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:

  • #ifdef
  • #if
  • #else
  • #elif
#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.

Predefined macros

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:

  • __DATE__: Jan 14, 2012
  • __GNUC__: Major version of GCC
  • __TIME__: 15:12:18
  • __INCLUDE_LEVEL__: The depth of includes starting with 0
  • __BASE_FILE__: The name of the current file

Towards infinity and beyond arguments

#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.)

X-Macro

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.

The problems

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))

Problem #1

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.

Problem #2

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.

Useful macros

// 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)

There we have fun

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).

A free car

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.

Get time

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);

Define Error

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;

Automated test generation

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;
}

RTFM

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!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn