2

Although I welcome answers on this on a general scope, I'm asking primarily for avr-gcc to make this not too broad.

I have looked at some questions, particularly this and this one. They mostly look at semantic differences, e.g. that a static const can't be used in place of a constant expression. However, although talking about memory allocation in general, they don't speak about optimization.

Let's look at an example:

typedef struct {
    GPIO_TypeDef *port;
    uint8_t  pin;
} gpio_pin_t;

static inline void gpio_pin_set(gpio_pin_t gpio, bool set) {
    if(set) GPIO_SetBits  (gpio.port, 1 << gpio.pin);
    else    GPIO_ResetBits(gpio.port, 1 << gpio.pin);
}

//elsewhere including above definitions

static const gpio_pin_t gpio_pin = {GPIOE, 8};

void some_function(bool enable) {
    gpio_pin_set(gpio_pin, enable);
}

As you can see, I'm using structs, so the third established way (enum) is not an option here

Can I expect gcc to optimize the gpio.port and gpio.pin accesses in the inline function? If not, why so, and does gcc apply other optimizations when it sees consts?

In its full generality, what do I lose optimization-wise by using static const variables instead of defines, especially looking beyond simple integer constants?

Silly Freak
  • 4,061
  • 1
  • 36
  • 58

2 Answers2

3

It depends on compiler implementation.

If you never take it's address and the symbol is not exported (for C it is by default, hence you must use static) then the optimizer should kick in and optimize it. For simple types (int, float) you can reasonably expect it to work across the board, but for structs - better check yourself what the compiler does. For a simple struct like your my GCC optimize it, eliminating the struct and passing it's values directly, loading constants to CPU registers. For larger structures it may decide it's not worth it.

You may generate assembly listing of the code to check:

avr-gcc -O<opt> -S somefile.c

or

gcc -O<opt> -S somefile.c

Do not forget about optimization level!

Using #define sucks, because preprocessor is really dumb - it's just replaces code literally before it's compiled. Using const buys you better type safety. Consider this example:

#define BYTE_VALUE 257
static const uint8_t sc_value = 257; // at least will show warning
int my_byte = BYTE_VALUE; // no warning, but obviously it's a bug!
ezaquarii
  • 1,914
  • 13
  • 15
  • 2
    The type-safety point is moot (though often claimed): `#define BYTE_VALUE ((uint8_t)257)` is as type-safe as `sc_value`. And `int my_byte = BYTE_VALUE;` is neither obviously wrong nor necessarily generates a warning. – mafso Mar 05 '15 at 14:34
3

Regarding abstraction:

Why do you drown such a simple thing in a flood of abstraction layers? You can safely assume that every C programmer knows the meaning of

PORT |= (1<<pin);
PORT &= ~(1<<pin);

This is as readable as the code can get, as far as a C programmer is concerned. By hiding this code in abstraction layers, you make your code less readable to C programmers, although it may very well get more readable to non-programmers. But you want the former to read your code, not the latter.

The above is also the fastest possible, in terms of efficiency. It is quite likely that the compiler will translate such code directly to a single bit set/bit clear assembler instruction.

So my advise is to throw all the abstraction out. You don't want to hide the hardware away from the embedded C programmer. What you do need to hide away from them is the specific hardware implementations, so that they need not re-invent the wheel for every new project.

One such abstraction is when writing a hardware-independent API layer. Such as for example void led_set (bool lit);. This function will light a LED on your board. You'd place it in the abstract file led.h, which has no corresponding .c file. Because the .c file is implemented for your specific project only: in my_led.c you'll have the actual implementation, which directly access the GPIO registers, sets up data direction & pull resistor regisers, handles signal polarity and so on.


Regarding your specific question:

There are no guarantees that GCC will inline that function as you expect: the inline keyword is pretty much obsolete, as the compilers nowadays are far smarter than programmers when it comes to deciding when to inline a function. I would say it is very likely, given that you compile with maximum optimization enabled. Only way to find out is to try.

But then it doesn't really matter the slightest whether or not the compiler inlines this function. You will not likely ever have such extreme real-time requirements that the function calling overhead will affect your program. We're talking tens of nano seconds here: not even the digital electronic integrated circuits on your board will respond fast enough for those extra CPU ticks to make a difference.

I work with MCU-based real-time embedded systems on daily basis and even in those systems, you rarely ever face a situation where extreme code optimization like this would matter. If you did, you would be using a DSP, not a regular MCU, most certainly not an AVR.

More importantly, your static const reduced the scope of the constant to the local file, so that none else need to concern itself with it, nor will you clutter down the global namespace. This is good programming practice, and good programming practice wins over manual code optimization in 9 times out of 10.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • To be fair, if he's using avr-gcc he may well start using a microcontroller. At 1MHz, this does start making a difference, especially in something you do as often as changing a pin state. If you were dealing with ultra low power stuff, this starts making a big difference (things staying on longer instead of sleeping). – BeB00 Nov 12 '16 at 20:25
  • @BWalker Maybe, though if you want to reduce power consumption of I/O pins, you would rather look into minimizing the amount of pin toggling. Most of the power consumption happens at the signal edge when the pin is turned on/off. – Lundin Nov 14 '16 at 07:14