Why are you recursively defining macros in terms of other macros in C?

I wanted to see how the arduino digitalWrite function actually works. But when I looked for the source for the function, it was full of macros, which themselves were defined in terms of other macros. Why should it be built this way and not just use a function? Is this just a bad coding style or the right way to do something in C?

For example, digitalWrite contains the macro digitalPinToPort .

 #define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) ) 

And pgm_read_byte - macro:

 #define pgm_read_byte(address_short) pgm_read_byte_near(address_short) 

And pgm_read_byte_near - macro:

 #define pgm_read_byte_near(address_short) __LPM((uint16_t)(address_short)) 

And __LPM is a macro:

 #define __LPM(addr) __LPM_classic__(addr) 

And __LPM_classic__ is a macro:

 #define __LPM_classic__(addr) \ (__extension__({ \ uint16_t __addr16 = (uint16_t)(addr); \ uint8_t __result; \ __asm__ __volatile__ \ ( \ "lpm" "\n\t" \ "mov %0, r0" "\n\t" \ : "=r" (__result) \ : "z" (__addr16) \ : "r0" \ ); \ __result; \ })) 

It is not directly related to this, but I also got the impression that double underscores should only be used by the compiler. Does LPM the __ prefix correct?

+5
source share
2 answers

If your question is β€œwhy use multiple macro levels?” Then:

  • why not? It is noteworthy that in the pre-C99 era without inline . The canonical example was the era of the 1980s [t21>, which was (IIRC) at that time (SunOS3.2, 1987), documented as a macro, with a note on the man page telling (I forgot the details) that with some FILE* filearr[]; a getc(filearr[i++]) was wrong (IIRC, at that time there was no undefined behavior terminology). When you looked at some system headers (for example, <stdio.h> or any header included by it), you can find the definition of such a macro. And at that time (with computers running at only a few MHz, therefore a thousand times slower than today), getc should be a macro for efficiency reasons (since inline does not exist, and compilers did not do interprocedural optimizations , as they can do now) . Of course, you can use getc in your own macros.
  • Even today, some standards define macros. In particular, today the waitpid (2) system call document is WIFEXITED and WEXITSTATUS as macros, and it is prudent to use #define some of your macros mix both of them.
  • The main thing is to understand the work of the C preprocessor and its deeply textual (so fragile) nature. This is explained in all the textbooks about C. Therefore, you need to understand what happens under the hoods.
  • a rule of thumb with the modern era of C (i.e., C99 and C11) is to systematically prefer to have some static inline function (defined in some header file) equivalent to a macro. In other words, #define some macro only when you cannot escape it. And I explicitly document this fact.
  • several macro levels (sometimes) increase code readability.
  • macros can be tested with #ifdef macroname , which is sometimes useful.

Of course, when you decide to define several levels of macros (I will not call them "recursive", read self-referencing macros ) you need to be very careful and understand what happens to all of them (combined and separately). It is useful to look into the pre-processed form.

By the way, to debug complex macros I sometimes do

 gcc -C -E -I/some/directory mysource.c | sed 's:^#://#:' > mysource.i 

then I look at mysource.i and sometimes even compile it, perhaps as gcc -c -Wall mysource.i , to get warnings that are in the pre-processed form of mysource.i (which I can check in the emacs editor). The sed command "comments" lines starting with # that set the source location (Γ  la #line ) .... Sometimes I even do indent mysource.i

(in fact, I have a special rule for my Makefile )

Does LPM have the __ prefix proper?

By the way, names starting with _ (standard and conditionally) are reserved for implementation. Basically, you are not allowed to have your names starting with _ , so there can be no collision.

Note that the __LPM_classic__ macro uses statement-expr extension ( GCC and Clang )

See also other programming languages. Common Lisp has a completely different macro model (and much more interesting). Read about hygiene macros . My personal regret is that the C preprocessor never developed to be much more powerful (and Scheme-like). Why this did not happen (imagine that the C preprocessor can call the Guile code to expand the macro!), Probably for sociological and economic reasons.

You still need to sometimes use some other preprocessor (like m4 or GPP ) to generate the C code. See autoconf .

+6
source

Why should it be built this way and not just use a function?

The goal is likely to optimize function calls using pre-C99 compilers that do not support built-in functions (using the inline ). Thus, the entire stack of functionally similar macros is essentially combined by the preprocessor into one block of code.

Every time you call a function in C, the overhead is tiny, due to jumping around the program code, managing the stack frame and passing arguments. This cost is negligible in most applications, but if a function is called very often, it can become a performance bottleneck.

Is this just a bad coding style or the right way to do something in C?

It is difficult to give a definitive answer, because the coding style is a subjective topic. I would say, consider using built-in functions or even (better) letting the compiler embed them yourself. They are type safe, more readable, more predictable, and with proper help from the compiler, the net result is essentially the same.

Related links (this is for C ++, but the idea is usually the same for C):

+4
source

Source: https://habr.com/ru/post/1262474/


All Articles