Author Topic: what the hell does "uint8_t const foo" do?  (Read 3934 times)

0 Members and 1 Guest are viewing this topic.

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 3719
  • Country: us
Re: what the hell does "uint8_t const foo" do?
« Reply #25 on: December 31, 2023, 08:47:33 pm »
Reading that link above, what is the difference (in terms of read-only protection) between

const uint32_t fred = 2;

and

#define fred 2

The latter doesn't obey C scoping and lifetime rules and you can't take the address of it. If you use simple literals without casts it is limited to only a few possible types (i.e., you can't make a 'short' literal).  If you use casting or complex expressions then you can fall victim to weird macro expansion / parsing problems such as order of operations.  In short, #define works OK for simple numeric literals, but rapidly becomes worse than using const variables when you move beyond that.  Because there is no name bound to the value, there is no chance for the compiler to detect conflicting definitions in multiple translation units.

The only real advantage to #define is that it can be used in places that require a compile time constant, such as array sizes.  This is not a problem in C++, which allows:

Code: [Select]
const int size=5;
int data[size];

I'm not saying to stop using #define for constants, there are still cases where it is advantageous, and due to long history of usage is considered idiomatic.  However, for a lot of cases, defining a global constant variable is better.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 3719
  • Country: us
Re: what the hell does "uint8_t const foo" do?
« Reply #26 on: December 31, 2023, 08:58:48 pm »
Unfortunately, C does allow the assignment of  const type*  to  type* pointers. Even without cast.
Gcc only spits out a warning. Example: https://godbolt.org/z/jfE1jdaEP
So you can indirectly modify const objects.

OTOH, C++ does not allow this conversion, neither implicitly, nor with static_cast(), nor with reinterpret_cast().
You explicitly need to use a const_cast(), or a traditional C-style cast (which is rather taboo in C++ anyway) to do that, so it can hardly happen by accident.

Yeah, this is basically due to const being added to C after it was in wide use, so a lot of code was written without const, or even without considering the possibility of constness -- for instance you can't implement the standard library strchr and strstr functions without casting away const.  I guess the thinking was that it would be easier to be adopted if they were a little sloppy with it, since otherwise it's hard to add const in one place without converting your whole codebase at once.  C++ has has const since near the beginning, so was able to be stricter with it.
 

Online gf

  • Super Contributor
  • ***
  • Posts: 1182
  • Country: de
Re: what the hell does "uint8_t const foo" do?
« Reply #27 on: December 31, 2023, 09:30:08 pm »
The only real advantage to #define is that it can be used in places that require a compile time constant, such as array sizes.  This is not a problem in C++, which allows:

Code: [Select]
const int size=5;
int data[size];

For automatic arrays, the following also works works in C with -std=c99 and later:
Code: [Select]
void f()
{
    const int size=5;
    int data[size];
}

For global and static arrays, constexpr and -std=c23 are required:
Code: [Select]
constexpr int size=5;
int data[size];
 

Offline dorkshoei

  • Frequent Contributor
  • **
  • Posts: 499
  • Country: us
Re: what the hell does "uint8_t const foo" do?
« Reply #28 on: December 31, 2023, 11:47:00 pm »
Lots of talk here about #define. For some cases consider using enum instead. A symbolic debuuger can do better job of reporting the symbolic value vs if you just #define.   Obviously this has only a loose connection to const issue
 

Online Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11648
  • Country: my
  • reassessing directives...
Re: what the hell does "uint8_t const foo" do?
« Reply #29 on: January 01, 2024, 06:51:30 am »
#define not only to assign constants, it also for code shortcut such as
#define MY_CODE ((a + myfunc(b)) / c)
and its not stored anywhere in program, its only allocated during compile time by compiling machine.
so its not your runtime thing...
« Last Edit: January 01, 2024, 06:53:08 am by Mechatrommer »
Nature: Evolution and the Illusion of Randomness (Stephen L. Talbott): Its now indisputable that... organisms “expertise” contextualizes its genome, and its nonsense to say that these powers are under the control of the genome being contextualized - Barbara McClintock
 

Offline pgo

  • Regular Contributor
  • *
  • Posts: 69
  • Country: au
Re: what the hell does "uint8_t const foo" do?
« Reply #30 on: January 01, 2024, 11:06:28 am »
#define not only to assign constants, it also for code shortcut such as
#define MY_CODE ((a + myfunc(b)) / c)
and its not stored anywhere in program, its only allocated during compile time by compiling machine.
so its not your runtime thing...
Or just write it as a function and expect any decent compiler to inline it anyway?
If the expression is not a constant then using a macro would still mean it would still "exist" in the code so it is still stored somewhere in program as a fragment.
If constant than the function would again be optimised to a constant value at compile time.
Just don't be clever with macros, please!!!
 

Online Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11648
  • Country: my
  • reassessing directives...
Re: what the hell does "uint8_t const foo" do?
« Reply #31 on: January 01, 2024, 12:12:31 pm »
Or just write it as a function and expect any decent compiler to inline it anyway?...
Just don't be clever with macros, please!!!
there are some circumstances simply making it a function will not do.. much like a construct with different values to setup before making a call.. and it happens at various places in your code. fixing or changing the #definition will fix all. if you havent found such condition then good for you.
Nature: Evolution and the Illusion of Randomness (Stephen L. Talbott): Its now indisputable that... organisms “expertise” contextualizes its genome, and its nonsense to say that these powers are under the control of the genome being contextualized - Barbara McClintock
 

Offline DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 5912
  • Country: es
Re: what the hell does "uint8_t const foo" do?
« Reply #32 on: January 01, 2024, 12:24:08 pm »
Macros are very efficient sometimes.
For example, I do use them for GPIO ports.
CubeIDE makes easy to name the pins to XX, generating XX_GPIO_Port and XX_Pin, for example a LED.
(Or just by hand in any header)

Instead doing PORT->IDR/ODR/BSRR etc every time (Long and messy) I use these macros.

Code: [Select]
#define CON2(a,b)       a##b
#define WritePin(p,o)   CON2(p,_GPIO_Port->BSRR) = CON2(p,_Pin)<<(o ? 0 : 16)
#define ReadPin(p)      ((CON2(p,_GPIO_Port->IDR) & CON2(p,_Pin)) && 1)
#define TogglePin(p)    CON2(p,_GPIO_Port->ODR) ^= CON2(p,_Pin)
Code: [Select]
#define LED_Pin          LL_GPIO_PIN_0
#define LED_GPIO_Port    GPIOB
Code: [Select]
TogglePin(LED);
ReadPin(LED);
WritePin(LED, 1);
« Last Edit: January 01, 2024, 05:13:37 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8173
  • Country: fi
Re: what the hell does "uint8_t const foo" do?
« Reply #33 on: January 01, 2024, 12:39:50 pm »
Or just write it as a function and expect any decent compiler to inline it anyway?

You can use always_inline attribute to force this, if you need to. The choice between macro vs. function should not be about perceived efficiency, but type requirements:
* When you need one type, use a function, to get type checking
* When you need generics over many potential types, use macros. That's the C way; C++ has templates.
 
The following users thanked this post: dare

Online gf

  • Super Contributor
  • ***
  • Posts: 1182
  • Country: de
Re: what the hell does "uint8_t const foo" do?
« Reply #34 on: January 01, 2024, 02:39:41 pm »
Or just write it as a function and expect any decent compiler to inline it anyway?

You can use always_inline attribute to force this, if you need to. The choice between macro vs. function should not be about perceived efficiency, but type requirements:
* When you need one type, use a function, to get type checking
* When you need generics over many potential types, use macros. That's the C way; C++ has templates.

For the calculation of compile-time constant expressions, you still may need to use macros in C, since (unlike C++) C23 does not support constexpr functions.

EDIT: I mean:

This works in C, of course (with macro): https://godbolt.org/z/xcT533rso

But this does not work in C: https://godbolt.org/z/69cMnGo8r

However, with constexpr, this works in C++: https://godbolt.org/z/fvvMoKaav
« Last Edit: January 01, 2024, 04:51:45 pm by gf »
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 3719
  • Country: us
Re: what the hell does "uint8_t const foo" do?
« Reply #35 on: January 01, 2024, 04:37:11 pm »
Or just write it as a function and expect any decent compiler to inline it anyway?...
Just don't be clever with macros, please!!!
there are some circumstances simply making it a function will not do.. much like a construct with different values to setup before making a call.. and it happens at various places in your code. fixing or changing the #definition will fix all. if you havent found such condition then good for you.

Not sure what you mean by that but your example sure was a textbook example of a macro that should be an inline function.  "Computing some values and calling another function" is a textbook example of what functions do.

DavidAlfas example with GPIOs used token pasting which is indeed something you can't do with a simple function call.  If you have painted yourself into a corner by using separate #defines for the GPIO port and pon, go ahead and do that but for everyones sanity don't design that on purpose.  It's much cleaner to use a GPIO_pin_t stuct which you can write simple functions to manipulate.
 

Offline DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 5912
  • Country: es
Re: what the hell does "uint8_t const foo" do?
« Reply #36 on: January 01, 2024, 05:15:44 pm »
For me it's much easier to manipulate a header with pin declarations than diving into the files?  :-//
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 828
Re: what the hell does "uint8_t const foo" do?
« Reply #37 on: January 01, 2024, 06:02:52 pm »
Quote
I use these macros
I already said too much on this subject in another thread, but if you actually use these macros your TogglePin macro uses ^= which leaves an opportunity to corrupt any of the 15 other pins on the port when used in multiple run levels/irq levels. Simply using the other two macros as a replacement would be a solution- #define TogglePin WritePin( p, ReadPin(p) ).

You can live without defines/macros a lot more than you think-
https://godbolt.org/z/Mza8Ef6x1

 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 3719
  • Country: us
Re: what the hell does "uint8_t const foo" do?
« Reply #38 on: January 01, 2024, 08:18:04 pm »
For me it's much easier to manipulate a header with pin declarations than diving into the files?  :-//

That doesn't really make any sense.  You can put it in the header files or "dive into the files" whatever that means.

Your example has a few major shortcomings beyond the (potential) concurrency issue cv007 mentioned.  The biggest one is that the poor bastard who has to read:

Code: [Select]
toggle_pin(LED);

and tries to look up LED will find that it is not defined anywhere in the source code.  It's not a macro, it's not a global variable, it's non existent and can't be used outside the argument of toggle_pin or it's friends.

As a consequence of this, you simply can't write a function that takes a pin as an argument nor can you store it in a larger data structure.  To do that you have to add more macros, or fall back to LED_GPIO_Port and LED_Pin, and now you have two separate and incompatible ways to refer to the same thing.

Again, using token pasting is OK if the alternative is major refactoring or changing vendor code that you don't want to touch, but your approach is not something anyone should do by choice.  It's much better to do something like the following:

Code: [Select]
// my_gpio.h

typedef struct {
  GPIO_Base *port;
  int pin_idx;
 } gpio_pin_t;

static inline write_pin(gpio_pin_t target, int val)
{
        target.port->BSRR = 1 << (target.pin_idx + (val ? 0 : 16));
}

const static gpio_pin_t LED1 = {GPIOA, 1};
const static gpio_pin_t LED2 = {GPIOD, 3};

// main.c
int main()
{
    write_pin(LED1, 1);
}

now you can write any function you want to accept a gpio_pin_t, it will be type checked, you can store it in a struct for later use and all the identifiers are easily searchable.  You can keep all your GPIO name assignments in one header flie, or declare them in the module in which they are used.  You can put them in .c files and avoid polluting the global namespace but still able to pass them to generic functions.
 
The following users thanked this post: newbrain

Offline pgo

  • Regular Contributor
  • *
  • Posts: 69
  • Country: au
Re: what the hell does "uint8_t const foo" do?
« Reply #39 on: January 01, 2024, 11:28:40 pm »
There is lots of interesting discussion of the advantages of Macros.
Personally I don't like them for several reasons (some already given):

- They are a textual substitution.  This is very powerful in some ways but it also leads to strange and difficult to track down bugs.  For example, if you #define a symbol then it is substituted almost everywhere.  I have wasted time tracking down strange bugs when a macro defined in a header file happens to have the same name as a structure member name - really weird messages from the compiler!  Yes I know - use capitals for the macro and lowercase elsewhere BUT have a look in the standard header files for most CPUs - full of structs waiting to be broken.
- Lack of scoping - related to the above.  A name defined somewhere applies everywhere.
- Novices especially have trouble with defining macros correctly - correct use of brackets, unintended effect of multiple evaluation side-effects etc.

Personally I use C++ so I can use templates (wait for the hue and cry!) to achieve many of the things that would be done with clever macros.  The macro examples given earlier for GPIO can be safely done with templates.  These are equally efficient and can be in namespaces to limit name collisions if needed.

bye
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: gb
  • Doing electronics since the 1960s...
Re: what the hell does "uint8_t const foo" do?
« Reply #40 on: January 02, 2024, 08:42:35 am »
STM use macros heavily for setting up GPIO and peripherals.

What I don't like is macros that generate code, rather than just doing compile-time calcs. Then you end up with stuff in .h files which generates code. It can be damn confusing when you are trying to create stuff like a boot block which jumps to a given address, and the code at that address is not what you expected :)
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8173
  • Country: fi
Re: what the hell does "uint8_t const foo" do?
« Reply #41 on: January 02, 2024, 09:57:50 am »
For me it's much easier to manipulate a header with pin declarations than diving into the files?  :-//

Wut, diving into what files? I mean, code has to be in files, where else? Just put the functions into those same files where you now put macros. static inline exists just for this reason, the functions can be in header files, which can be included and included again in every compilation unit if you so wish, just like macros.

The only reason to use macros is when some macro-exclusive feature is actually needed, e.g. absence of type checking (allowing generic "template"-like thing), or text concatenation/stringify operations. Also great for generating compile-time arrays etc, I prefer them over separate code generation scripts.

Otherwise than that, macros require a lot of care:

1) You need to wrap them in do{} while(0) which looks ugly and is unnecessary boilerplate. If you fail to do that, macro cannot be used like a function e.g.
Code: [Select]
#define LED_ON() {GPIOA->BSRR = 1UL<<5;}

if(condition)
    LED_ON(); // expands to {GPIOA->BSRR = 1UL<<5;};, which has one semicolon too much, so that the else below doesn't belong to anywhere
else
   something_else();

Fixed:
Code: [Select]
#define LED_ON() do{GPIOA->BSRR = 1UL<<5;}while(0)

if(condition)
    LED_ON(); // expands to do{GPIOA->BSRR = 1UL<<5;}while(0);   -- just the right number of semicolons
else
   something_else();

2) You lose type checking so need to be more careful everywhere in code

3) You have to split lines with trailing \, which is tedious,

4) You have to wrap all arguments inside ():

Fail:
Code: [Select]
#define MACRO(arg) (arg*2)

MACRO(5); // 5*2 = 10 -> ok
MACRO(5+1); // 5+1*2 = 7 -> fail

OK
Code: [Select]
#define MACRO(arg) ((arg)*2)

MACRO(5); // (5)*2 = 10 -> ok
MACRO(5+1); // (5+1)*2 = 12 -> ok

5) For macros that access the argument(s) more than once, you need to make a temporary variable:

Fail:
Code: [Select]
#define ENDIAN_SWAP(arg) (((arg)&0x00ff)<<8 | ((arg)&0xff00)>>8) // very common bug, looks fine, but...

ENDIAN_SWAP(i++); // fails, when it becomes: (((i++)&0x00ff)<<8 | ((i++)&0xff00)>>8), i is incremented twice, in undefined order.

Fuck this, I don't even try to fix it with a temporary variable, because macros do not have return, maybe you could do something with the , operator no one normally uses. Instead, let's be reasonable and do this properly:
Code: [Select]
static inline uint16_t ENDIAN_SWAP(uint16_t arg) { return (arg&0x00ff)<<8 | (arg&0xff00)>>8; } // look how many unnecessary parenthesis we removed

ENDIAN_SWAP(i++); // works now as expected.


TLDR, only use macros when they offer something useful, not "just because".
« Last Edit: January 02, 2024, 10:13:44 am by Siwastaja »
 
The following users thanked this post: newbrain

Online gf

  • Super Contributor
  • ***
  • Posts: 1182
  • Country: de
Re: what the hell does "uint8_t const foo" do?
« Reply #42 on: January 02, 2024, 10:05:21 am »
Fuck this, I don't even try to fix it with a temporary variable, because macros do not have return,

Gcc has statement expressions ({ ... }), but it's a proprietary extension.
 
The following users thanked this post: Siwastaja

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8173
  • Country: fi
Re: what the hell does "uint8_t const foo" do?
« Reply #43 on: January 02, 2024, 11:24:54 am »
Wut, diving into what files? I mean, code has to be in files, where else?
i think what he meant is that, if we use #define, its usually in top header file, and then we use it in various places in code files... if we need to change pin number or logic, we just modify the top section of header file, no need to dig in every part in code file if we dont use #define. if we use function instead of #define, we still need to dig where is that function in code file.

I don't understand. You can put the static inline function definition EXACTLY where you would put the macro definition. If you need to modify it, you modify it where it is, in one place. Exactly the same thing.
 

Online Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11648
  • Country: my
  • reassessing directives...
Re: what the hell does "uint8_t const foo" do?
« Reply #44 on: January 02, 2024, 11:29:31 am »
Wut, diving into what files? I mean, code has to be in files, where else?
i think what he meant is that, if we use #define, its usually in top header file, and then we use it in various places in code files... if we need to change pin number or logic, we just modify the top section of header file, no need to dig in every part in code file if we dont use #define. if we use function instead of #define, we still need to dig where is that function in code file. you all sounds too beginner to understand this? its not easy to find a function implementation in the middle of a large *.c file esp if the programmer used prototype declaration and then too many calls prior to the actual function body. at least we need some few clicks on find tool and look carefully are we looking at actual body? or just a function call? he mentioned about "uniformity" i "think" i know exactly what he meant ;D sometime i can understand "uniformity" as "tidiness" or "logically-correct" arrangement. ymmv.

TLDR, only use macros when they offer something useful, not "just because".
how about... just because it offers something useful? ;D

having interuppting all these relative or personal favourite issues, i didnt mean to encourage nor discourage the use of #define, its just a tool c/c++ provided. if you think its useful, go for it, everyone has they own coding style, i'm not interested in politically correct (relative thing) discussion. ymmv.

Wut, diving into what files? I mean, code has to be in files, where else?
i think what he meant is that, if we use #define, its usually in top header file, and then we use it in various places in code files... if we need to change pin number or logic, we just modify the top section of header file, no need to dig in every part in code file if we dont use #define. if we use function instead of #define, we still need to dig where is that function in code file.
I don't understand. You can put the static inline function definition EXACTLY where you would put the macro definition. If you need to modify it, you modify it where it is, in one place. Exactly the same thing.
too much to type believe me... if we need to modify arguments, we have "double work" ...

(ps: sorry for the edited text, i hate "last edited" signature on my post...)
Nature: Evolution and the Illusion of Randomness (Stephen L. Talbott): Its now indisputable that... organisms “expertise” contextualizes its genome, and its nonsense to say that these powers are under the control of the genome being contextualized - Barbara McClintock
 

Offline DavidAlfaTopic starter

  • Super Contributor
  • ***
  • Posts: 5912
  • Country: es
Re: what the hell does "uint8_t const foo" do?
« Reply #45 on: January 02, 2024, 12:12:52 pm »
"There are 1000 ways of changing a bulb" :-+
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 828
Re: what the hell does "uint8_t const foo" do?
« Reply #46 on: January 03, 2024, 01:17:37 am »
Quote
if we need to change pin number or logic, we just modify the top section of header file, no need to dig in every part in code file if we dont use #define

Modifying my previous example, although commenting out the includes/pragma once is required for the online compiler so have to understand these sections will live in different files as noted-
https://godbolt.org/z/o8bdbvzx9
If that led changes to another pin, still only 1 place to change. Also note since the online compiler had all these functions accessible in the single file, it inlined test1/test2 although they are still callable since they are not marked static.

The defines may have been a requirement in the distant past due to not so smart compilers, but the modern compiler is quite capable of optimizing if you give it the right information (static/const/etc). Whether one wants to go this far or not is up to each user. As noted, there are still some things you just have to get the pre-processor to do, and dealing with strings is one of them.

I switched to using mostly c++ some years ago, and at the same time also set out to eliminate creating any define unless no other choice. It takes some effort to do so as the 'standard' way of using defines for just about everything has been around a long time, and tends to becomes your first thought (I need x- create a define for it, I need y- create a macro). Once that habit is broken, it is actually pretty nice to be free from these things, but then still have to deal with code where defines still rule (manufacturers code/headers is one such place). Using c++ does make this easier to do, but you can still go a long ways in c if you want to (want being the key word). Just you and the compiler one-on-one, no middleman which routed/modified your input through an unknown number of headers.

If was in charge of an mcu programming 101 course, I would prohibit the creating of any define and make the student learn the language. Whether or not it is useful for them in later stages (like strings), at least the habit will not have been set which normally treats every problem as having a solution involving a define. I am not in charge of anything, so no need to worry.

 

Offline Tation

  • Contributor
  • Posts: 38
  • Country: pt
Re: what the hell does "uint8_t const foo" do?
« Reply #47 on: January 03, 2024, 05:06:27 am »
I switched to using mostly c++ some years ago, and at the same time also set out to eliminate creating any define unless no other choice.

Interestingly, when I switched to C++ for embedded projects, also a few years ago, I also desired to be freed from macros, specially the clever ones. After this time I can say that it is possible (in C++) to live without them (well, almost, vendor's #defines are still there). Maybe it is that I have not yet confronted the problem which is hard with constexprs or templates or plain functions, but easy with macros.

And no, there's neither Flash size increase nor speed decrease, but much more readable code instead.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf