A C Macro that reference another macro - Page 1
Forum section: electronics / general

author: jealcuna
author_rank: Contributor
timestamp: November 02, 2021, 03:12:41 am
content: I have the following macros:(see attached picture)I have a c variable uint8_t type and I would like to call these macros following each bit of the variable. So I wonder if there is an elegant way to accomplish this rather than use a if else for each bit.

author: blacksheeplogic
author_rank: Frequent Contributor
timestamp: November 02, 2021, 03:15:51 am
content: Why not just pass the bit mask to the macro?

author: golden_labels
author_rank: Super Contributor
timestamp: November 02, 2021, 03:20:33 am
content: Please post text as text, not a picture.Also describe the problem properly. Currently you have posted some picture, containing defines that aren’t even macros taking arguments, yet you ask about something seemingly taking arguments, which is used in a way that you haven’t explained, then you ask how to achieve the effect you forgot to describe.If you can’t form a description, at least show us aminimalpiece of code that depicts such a macro being used.

author: jealcuna
author_rank: Contributor
timestamp: November 02, 2021, 03:30:42 am
content: For a moment assume that bitmask manipulation is not possible.So this is a basic piece of code:uint8_t variable;...variable = 2; // 0b00000010, only OUT_02_ON should be called, for the others OUT_0X_OFF...if(variable&1){OUT_01_ON;}else{OUT_01_OFF;}if(variable&2){OUT_02_ON;}else{OUT_02_OFF;}if(variable&4){OUT_03_ON;}else{OUT_03_OFF;}...if(variable&128){OUT_08_ON;}else{OUT_08_OFF;}Is there a more elegant solution for this referencing macros?

author: ledtester
author_rank: Super Contributor
timestamp: November 02, 2021, 03:57:19 am
content: What don't you like about your code?Here's another idea - which you may or may not find acceptable:Code:switch (variable) {case 0:OUT_01_OFF;OUT_02_OFF;OUT_03_OFF;...break;case 1:OUT_01_ON;OUT_02_OFF;OUT_03_OFF;...break;case 2:OUT_01_OFF;OUT_02_ON;OUT_03_OFF;...break;...253 more case statements...}All of this code can be generated by a program so you don't have to type it in.

author: golden_labels
author_rank: Super Contributor
timestamp: November 02, 2021, 02:47:11 pm
content: In the presented code macros are not used in any way, that would be related to the question. You are invoking them, but they have nothing to do with howvariableis used or you have failed to explain that connection. So for now I am ignoring the subject of macros, unless you clarify what did you mean.The more elegant way to do that may be to use bit shifts in a loop, which you have refused to use for some reason (possiblyXY problem?). An alternative approach might be a look-up table. But if any of those is suitable for your particular purpose depends on what youactuallywant to achieve. It’s possible that the problem is howvariableis structured, preventing you from using a better solution.We’re missing the definitions ofOUT_…macros, but it seems you are using object-like macros to generate runtime code. That is confusing to the reader. Consider turning them into function-like macros: to make it clear you are executing something at runtime. There is also a hard to ignore chance of two more mistakes: silently referring to the local context and using macros instead of functions.

author: Jeroen3
author_rank: Super Contributor
timestamp: November 02, 2021, 03:00:33 pm
content: Quote from: ledtester on November 02, 2021, 03:57:19 amCode: (python)for x in range(8):print('if(variable & (1<<x)){{OUT_0{x}_ON;}}else{{OUT_0{x}_OFF;}}'.format(x=x))Try it.It's weird, but if you want to you can have code write code that generates assembler that compiles to instructions.This is not one of the situations you want that.I'd suggest looking up pointers.For example:Code:typedef struct {int number;gpio_t GPIO} io_assignment;io_assignment outputs = {/* out 0 */ { 5, GPIOB},/* out 1 */ { 2, GPIOF},/* out 2 */ { 1, GPIOD},...// usageoutputs->GPIO->SET = 1Like this?

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: November 02, 2021, 03:28:20 pm
content: Quote from: jealcuna on November 02, 2021, 03:30:42 amYou can wrap a single `if` from your example into a separate macro layer, thus obtaining something likeCode:#include <stdio.h>#include <stdint.h>#define OUT_01_ON printf("1 on\n")#define OUT_01_OFF printf("1 off\n")#define OUT_02_ON printf("2 on\n")#define OUT_02_OFF printf("2 off\n")#define OUT_03_ON printf("3 on\n")#define OUT_03_OFF printf("3 off\n")#define TEST_I(variable, I) if (((variable) & (1u << (I) - 1)) != 0) OUT_0##I##_ON; else OUT_0##I##_OFFint main(){uint8_t v = 0x2;TEST_I(v, 1);TEST_I(v, 2);TEST_I(v, 3);}http://coliru.stacked-crooked.com/a/0e546d6fefed3de6However, once you get to a two-digit indices things might become more complicated: you will either have to abandon the aligned `01`, `02` ,... approach when naming your macros or resort to extra macro-magic to make sure the proper pieces of macro names are generated for short indices. This is all perfectly doable, BTW.You can also build a "macro-cycle" on top of the above `TEST_I` example, thus effectively achieving exactly what you are asking for. However, it would require a bit of macro-magic framework, similar to Boost.Preprocessor, which will incapsulate all the tedious details. So, this is really a question of whether you want to include that framework into your code. Once you do, you will be able to do quite a lot with macros. But if this is just a one-off situation, then you might be better off using some simpler solution, even if it requires more typing.---Of course, a question that immediately pops to mind is why you even have that collection of macros? What are they doing? Could they possibly be folded into a single parameterized macro or function? That would likely open a way to much better solution, possibly not involving macros at all.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 02, 2021, 05:48:53 pm
content: Quote from: jealcuna on November 02, 2021, 03:30:42 amUh, sure. Although I don't think I've ever seen this using a C compiler.So I'm just wondering - are bitwise operations so inefficient on your particular target that tests would be more efficient (which sounds odd), or are they just not available, meaning you actually use a crippled C compiler - maybe something like for those Padauk MCUs... Just curious.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 03, 2021, 08:18:54 am
content: Quote from: jealcuna on November 02, 2021, 03:30:42 amSo your example of no bitmask manipulation is actually a bitmask manipulation."variable&whatever"isa bitmask.This uses a macro to reference another macro, might be what you're searching for:Code:#define OUT_OFF 0 // 00: OFF#define OUT_ON 1 // 01: ON#define OUT_TOGGLE 2 // 10: TOGGLE#define OUTS 8 // Number of outputs#define OUT_0 LATA0 // Declare anything as the outputs#define OUT_1 LATA1#define OUT_2 LATB5#define OUT_3 LATC1#define OUT_4 LATA7#define OUT_5 LATB1#define OUT_6 LATB4#define OUT_7 LATC5#define OUT(n) OUT_##n // Convert OUT(n) into OUT_N to reference the upper macrosHowever you can't use a that macro with a dynamic variable!Ex. You can do this at compile time: OUT(1)But you can't do OUT(variable).So you'll need a const struct that defines the port and the pin.Also, to save space, instead using 3 bits for each output (OFF/ON/TOG modes), use 2 bits and decode them to get 4 states (0-3).Code:#define OUT_OFF 0 // 00: OFF#define OUT_ON 1 // 01: ON#define OUT_TOGGLE 2 // 10: TOGGLE#define OUT_ELSE 3 // 11: SOMETHING ELSE?#define OUTS 8 // Number of outputstypedef const struct {PORT * port; // Pointer to GPIO PORT. This can be GPIO* , uint32_t*, uint8_t*...uint8_t pin; // Pin number}out_t;out_t outs = { = { // PORT 0.port = &GPIOA,.pin = 0,}, = { // PORT 1.port = &GPIOA,.pin = 1,}, = { // PORT 2.port = &GPIOB,.pin = 4,}, = { // PORT 3.port = &GPIOC,.pin = 6,}, = { // PORT 4.port = &GPIOC,.pin = 1,},// etc ...};void update_outs(uint16_t var){ // 2 bits per output, so 16 bit for 8 outputsfor(uint8_t n=0;n<OUTS;n++){ // 16 bits contents are 77.66.55.44.33.22.11.00switch( var>>(n*2) & 3 ){ // Extract the 2 bits we needcase OUT_OFF:*outs.port &= ~(outs.pin);break;case OUT_ON:*outs.port |= outs.pin;break;case OUT_TOGGLE:*outs.port ^= outs.pin;break;case OUT_ELSE:break;default:break;}}}

author: Doctorandus_P
author_rank: Super Contributor
timestamp: November 03, 2021, 11:26:04 pm
content: It's quite common to have macro's nested in C.But beware that the preprocessor is not very smart. It is just a text replacement feature, and this may lead to very unexpected results when macro's get nested. For this reason usually all macro's are surrounded by parenthesis

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 05, 2021, 12:52:51 pm
content: What's with the macro fetishism?If you use a half-decent C compiler, including SDCC, GCC, clang, Intel C, or even Microsoft C++, and you enable optimization (which you should, always), and you write code similar toCode:/* Pin names */enum {PIN_0 = 0,PIN_1,/* ... */PIN_N,};static inline void output_enable(const int pin){if (pin == PIN_0) {/* Code to enable pin 0 */} elseif (pin == PIN_1) {/* Code to enable pin 1 */} else/* Repetition omitted for brevity */if (pin == PIN_N) {/* Code to enable pin N */}}static inline void output_disable(const int pin){if (pin == PIN_0) {/* Code to disable pin 0 */} elseif (pin == PIN_1) {/* Code to disable pin 1 */} else/* Repetition omitted for brevity */if (pin == PIN_N) {/* Code to disable pin N */}}static inline void output_set(const int pin, const int state){if (state)output_enable(pin);elseoutput_disable(pin);}thenoutput_set(PIN_1, 0)will compile only to whatever you have in/* Code to disable pin 1 */without any conditionals. It is nowadays a trivial optimization that even SDCC supports, to detect that the pin number and state are compile-time constants, and inline only the relevant part of the function, omitting any conditional checks. If the pin is not a compile-time constant but the state is, then eitheroutput_enable()oroutput_disable()gets inlined or called. Only when neither are compile-time constants, does the actualoutput()inlined or called. If the pin numbers are then small consecutive integers, a jump table gets generated (assuming the target architecture supports it), in which case the "cost" of all those if clauses is just one conditional (for the state) and a computed jump: dirt cheap. (You can get much smaller code size in that case by using suitable tables, though, if you have lots of pins.)In C, you will want to use either enums or preprocessor macros to "configure" pins;const int MY_PIN = PIN_N;isnota compile-time constant (but bothenum { MY_PIN = PIN_N };and#define MY_PIN PIN_NmakeMY_PINa compile-time constant). In C++, you can also use enums of preprocessor macros, orconstexpr int MY_PIN = PIN_N.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 05, 2021, 06:35:00 pm
content: These days, I agree that inline functions - and let's not get into the whole inline discussion again, but yes, modern C compilers will inline as much as they can as long as definitions are in the same compilation unit, so the inline qualifier is often not even necessary - are the way to go if the only reason you use macros for is "efficiency".Now macros have their uses of course, every time they can avoid duplicating code. This is what they were meant for and what they are "good" at. Of course to be used with due care, since they only do text substitution. But I'll certainly favor well-written macros, even when nested several levels, to duplicating code.But for efficiency - just with the idea of "inlining" code - they do not make much sense anymore.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 06, 2021, 01:00:38 am
content: Yup. It probably makes sense to replace thestatic inlinewith a macro, sayHELPER_FUNCTIONor whatever you consider appropriate, that expands to a set of attributes your compiler prefers for such functions. For GCC, Clang, and Intel CC, I tend to use__attribute__((unused)) static inline, which really has very little to do with inlining: it just tells the compiler that this function does not need to be used (that is, do not issue a warning if it is not used), is only used in the current compilation unit so there is no need to generate a symbol referring to a callable version of the function, and the compiler is explicitly free to decide for each use whether to inline the function there or just emit a call to a common implementation (and if all instances are inlined, there is no need to have a common implementation at all).In the earlier days of C, the compilers really didn't do much optimization at all. Programmers used macros, because that way the compilers generated faster code: they saw each use of the macro as a separate piece of code, and just tried to generate the code to fit into the surrounding situation each time.Now, the C compilers doa lotof optimization. They detect use patterns –– for example, so much so that it nowadays doesn't matter to the compiler whether a local variable is markedconstor not (which is a promise to the compiler that the code does not attempt to change its value) since they detect if the variable is modified or not. I still do mark such variablesconst, but for a completely different reason: it helps us humans read the code, because a promise is a promise, and ifconst, we don't need to worry that the variable might be modified at some point. The compilers can tell, but we humans maintaining the code still need the help.It turns out that the macro trickery tends to becounterproductivewith these optimizing C compilers. It is like unrolling a loop, and then hoping that the compiler can convert the unrolled loop back to a loop if necessary. (These C compilerscan, and sometimes do, unroll loops; but re-rolling an unrolled loop back to a loop is nontrivial and complicated thing to do wrt. the behaviour dictated by the C standard, so that is just not something the compilers currently/normally do.)

author: Doctorandus_P
author_rank: Super Contributor
timestamp: November 06, 2021, 03:02:36 pm
content: If you've got 128 of them, then that is a lot of typing.What do these macro's actually do?Maybe it's a better solution to write a for loop, and then place your "macro thing" in one (or a few) arrays.Copy / paste code 128 times is also error prone.You may want to consider to write a small C program (Or Python or another script language) that generates the C source code in a text file.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 06, 2021, 06:30:20 pm
content: Quote from: Nominal Animal on November 06, 2021, 01:00:38 amOh yeah. Absolutely. Also keep in mind that some people, even to this day, still use pretty old or limited compilers. So in their particular case, using macros may be more efficient too.Now if you use a GCC version that is not over 20 years old, you shouldn't bother. But some older compilers from vendors can be pretty lame.Beyond efficiency, macros can still have an edge: they allow some form of meta-programming - making the end code easier to write and to look at, while strictly using functions would not allow some particular syntax or way to manipulate objects and constants. Sometimes a good macro is way nicer to handle than having to go through hoops with functions.

author: brucehoult
author_rank: Super Contributor
timestamp: November 06, 2021, 11:11:54 pm
content: Quote from: SiliconWizard on November 06, 2021, 06:30:20 pmHave a look at my use of macros in ...https://github.com/brucehoult/trv... and tell me if you think it's disgusting or not :-)I #define INSTRUCTION to four different expansions in different places, and then #undef it each time.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 07, 2021, 12:05:59 am
content: Quote from: brucehoult on November 06, 2021, 11:11:54 pmWell, I would probably have done it differently. In particular, I usually try to avoid redefining macros. But this is a matter of style. Otherwise I'm OK with it. I also use macros in similar ways. And the '##' operator is handy too.This shows how macros are useful in a creative way, and here allows to define instructions in just one place, while making it easy to add new instructions.(Now judging from what some say about the non-trivial use of macros here, I'm pretty sure they will find it awful.)

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 07, 2021, 02:34:05 am
content: Quote from: SiliconWizard on November 06, 2021, 06:30:20 pmYes, definitely. Even I have shown examples here from generics to basic polymorphism to expanding macros differently based on the number of arguments, all to make it easier for the programmer to implement useful things, without compromising the efficiency of the compiled code.Macros are an useful tool, but they have their limitations. It is using macros where something else works better that I referred to as "fetishism".(I find myself using the "when the only tool you have is a hammer, all problems look like nails" idiom too often, so I tried something else for once.)Quote from: brucehoult on November 06, 2021, 11:11:54 pmNot disgusting, just standard way of using the preprocessor for polymorphism, in my opinion. And very clean implementation, too.I myself posted a much more "disgusting-looking", but perfectly useful example of this kind of polymorphism,six months agoin the Embedded programming sub-board (Concurrency in a bare metal environment thread).The most annoying deficiency with the C preprocessor is that no string operations or matching is supported in preprocessor directives. You can compare and do basic arithmetic with integers, but basically nothing with strings. To me, it is a good reminder of the limitations of the preprocessor.One of the most common ways I use macros is default constants that are trivial to override at compile time:#ifndef DEFAULT_VALUE#define DEFAULT_VALUE 42#endifYou see, if using GCC/Clang/Intel CC, you just add a cimpile option, say-DDEFAULT_VALUE=99, to override the default.Another common use case is with preprocessor arithmetic, when I want to treat an undefined macro as a zero, and generate different code based on the macro value:#if VALUE-0 == 0printf("Value is zero or undefined\n");#elif VALUE-0 == 1printf("Value is one\n");#elseprintf("Value is defined, but neither one nor zero.\n");#endifWhen used in apreprocessor arithmetic expression, undefined macros are empty. So, ifVALUEis undefined, the preprocessor seesVALUE-0as-0which is the same as zero.This can be a bit confusing, because if you useVALUEin the C code, and it is not defined as a macro, it is then treated asVALUE(i.e., a normal identifier). It's kinda-sorta the opposite behaviour between the preprocessor and the compiler proper, really. But useful, nevertheless.

author: DiTBho
author_rank: Super Contributor
timestamp: November 07, 2021, 09:01:57 am
content: Quote from: Nominal Animal on November 06, 2021, 01:00:38 amgcc-v2.95 on BeOSgcc-v3.0.1 on Luna88kSierraC for TicalcMicrochip C for dspic30everything before ~2004

author: DiTBho
author_rank: Super Contributor
timestamp: November 07, 2021, 09:25:11 am
content: Quote from: Nominal Animal on November 07, 2021, 02:34:05 amlike this?Code:/** This macro allows you to obtain* a pointer to a structure* from a pointer to one of its members.*/#ifndef struct_from_field#define struct_from_field(cstruct, field, pointer) \((cstruct*) ((p_uint8_t) (pointer) - offsetof(cstruct, field)))#endifI reworked the whole source to remove this *magic* stuff

author: DiTBho
author_rank: Super Contributor
timestamp: November 07, 2021, 10:13:16 am
content: Quote from: Nominal Animal on November 07, 2021, 02:34:05 amDenx' u-boot is the best example of this.I reworked the code for PowerPC (40xx an PowerQUICC-I / II) and I found it full of macros that should be removed because there are better ways to work the code. I looked at 2008 code, but no nice, even in the 2021 version they insist on keeping that crap with their code and for example you have four macro level to manage PCIA macro that uses a macro that references a macro that has elements defined in another .h file, and during the process there are a lot of expansions not easy to track down.And it's not just "powerpc", if you look at the firmware of routers like the TL703, well it's "mips"... and it's terrible for the same reason.

author: brucehoult
author_rank: Super Contributor
timestamp: November 07, 2021, 10:13:51 am
content: Quote from: DiTBho on November 07, 2021, 09:01:57 amI don't recall gcc 2.95 being all that bad at optimisation. At least it produced correct code, unlike the first couple of years of gcc 3.x.

author: DiTBho
author_rank: Super Contributor
timestamp: November 07, 2021, 10:24:50 am
content: Quote from: brucehoult on November 07, 2021, 10:13:51 amOnBeOS-v5.*I remember it was not well optimized on an operating system that aimed to fly on hardware like PentiumIIs (SMP, up to four CPUs supported), so the code here and there was reworked with manual macros to get better performances, and 15-years later that code got reworked again onHaikufor gcc-v9Funny legacy

author: golden_labels
author_rank: Super Contributor
timestamp: November 07, 2021, 05:48:45 pm
content: It seems that the op lost interest in receiving the answer, if no one noticed.Therefore the discussion is somewhat pointless, because we do not know, what they wanted to do.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 07, 2021, 06:52:51 pm
content: Well yeah, as often. The discussion drifted off about macros in general, but the OP went away and never answered our questions. But this is usual and nothing special.

author: Kalvin
author_rank: Super Contributor
timestamp: November 08, 2021, 10:38:31 am
content: If you cannot or do not want to use use x-macro wizardry, why don't you just create a simple utility which will generate the required C source code into a file, which you can include into your project. Just add the code generator as a part of your project's makefile, and the tool will regenerate the source code as needed if the utility or its input data file changes.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 08, 2021, 04:26:40 pm
content: Alwaystrywith macro wizardry, though.Code generation is sometimes the unavoidable evil, but using external code generation when the actual language itself supports what is needed directly is just nuts.In order of preference, try to first make things work without macros, i.e., normal C code. Now if this is impossible, inefficient or results in unreadable code (or copy-pasting, whichisevil), use C preprocessor and its features and tricks to achieve what is needed. If this is impossible as well (or unreadable), then as the last resort, go for external code generation.

author: golden_labels
author_rank: Super Contributor
timestamp: November 09, 2021, 01:17:03 am
content: One should use the right tool for the job. C preprocessor is very primitive, tricky and using it quickly becomes more trouble than help. Being a language, that is completely separate from C proper, makes it even worse.If you need simple token replacement or concatenation, it’s perfectly fine. But the moment you move towards decision making and calculations, it turns into a horror. While it is Turing-complete,(1)it is unreadable, requires using an inconvenient input format, can’t be easily debugged and error messages are gibberish.Using a separate tool is much more convenient and, by all means, better. Even unnatural tools likem4are considerably better at most tasks you could imagine. Not to mention simply using any popular scripting language that appeared in the past 20 years. Python or Perl are easily obtainable, are open, are free, likely already present on developer’s computer anyway. Something that takes 500 unreadable of lines of C preprocessor, heavily dealing on already pre-generated Boost.Preprocessor, can be cleanly expressed in either of those in just few lines.____(1)Subject to resources limitation, which in the case of preprocessor metaprogramming must be manually pre-defined.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 09, 2021, 07:12:01 am
content: Quote from: golden_labels on November 09, 2021, 01:17:03 amI've usedawkto generate precomputed (trigonometric etc. fixed point) tables used on architectures without hardware floating point support, as simple header files. I do recommend splitting the generated part into a separate file you simply#includein the actual header or source file, instead of using some sort of templates (as certain development environments do), and use an easily recognized file name or extension for it. Keep It Simple, and consider the human who happens to have to maintain the code a year or two from now: it often is yourself, and you'll be glad then of the consideration. I am, almost constantly.I've even used a pre-build C program to generate (and verify perfectness) of a hash table of known keywords/tokens, to speed up configuration file parsing. Essentially, it uses the given hash function, the list of tokens, and generates the minimum size hash table that has no collisions, brute-forcing it, and emits the result as a nice C header file. (In this case, it is important to check that the host and target architectures use the same hash algorithm and hash size. If the hash algorithm works on a byte-by-byte basis, byte order does not matter.)I've also usedobjcopyto generate ELF object files directly from binary data, without processing it through the compiler. I usually use a scriptlet that generates a suitable header file describing the object too (including the size, which otherwise would be a link time constant, not a compile time constant).ELF-based toolchains have alotof useful tricks you can do to make maintenance easier. Usingsections to collect entries in different files into a single contiguous arrayis one of my favourites: the linker does all the hard work for you, and you only need to make sure that the size of the entries is a multiple of the alignment. You can even make it an sort-of-list of variable-length entries, if each entry begins with its size. Very useful for compile-time extensions, menus, and such on embedded environments, as all that data can be put into ROM/Flash, without having to run some "registration" functions at run time: if the facility is linked to the final binary, then the related menu entries are too. Simples.There are a lot of useful tools, from macros to data/code generation to pure binary tools, but without additional information on the task at hand, we're just discussing the various options and possible scenarios.@jealcuna, why did you leave us hanging?

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 09, 2021, 05:28:30 pm
content: And yes, as always, it all depends. Sometimes code generators are cool. Sometimes they are just added "noise". Depends on use case.The C preprocessor is not bad at all IMO. It has the merits of being well known, standard, and doing what we ask it to do. Oh I've tried alternatives in the past too. I too tried using M4. This looked promising at first, but I quickly stepped back. The experience was a lot more painful than expected, with the added issue that having your code maintained by other people would of course be more difficult.I mostly use code generators - as Nominal mentioned - to generate precomputed tables (including images). Not as meta-programming layer.