Function pointers: good or bad things?

"I think MISRA C disallows function pointers, partly for this reason."

Are function pointers really bad things?

Sincerely I found them useful and lastly I was using them more and more. Am I wrong?

Function pointers help me in at least three situation.

With function pointer I can isolate C modules from the rest of the project, so it is much more simple to create a well-define hardware abstraction layer that let me improve portability to other hw platforms, but mostly let me create a "simulator" on the development machine.

For example, consider a simple leds module interface:

int leds_init(void (*on_fn)(uint8_t idx), void (*off_fn)(uint8_t idx)); int led_on(uint8_t led_idx); int led_off(uint8_t led_idx); int led_toggle(uint8_t led_idx); int led_blink(uint8_t led_idx); int leds_on_all(void); ...

Here the important function is leds_init() with function pointers arguments that really switch on or off the LED idx. I call leds_init() in this way:

leds_init(bsp_led_on, bsp_led_off);

On target bsp_led_on() manages GPIO registers or SPI GPIO expander and so on, on development machine bsp_led_on() could be a printf() or a different icon on a GUI.

In this context, functions pointers help to have testability modules. If I want to test leds module, the test code could call leds_init() in this way:

leds_init(test_led_on, test_led_off);

So during tests it's much simpler to break the links between modules and insert the test suite where it is necessary.

Another nice thing that is possible with function pointers is to make some OOP tricks, for example polymorfism.

Of course one drawback of using function pointers is stack usage calculation (see my previous post): it would be impossible to make a calc, because the tool can't resolve the function pointer call.

Reply to
pozz
Loading thread data ...

He is, AFAIK, wrong - but I only looked at one version of MISRA (MISRA C

2012).

Yes.

No.

They are useful, but they are also bad !

There is no doubt that function pointers are useful for this kind of thing. But really, what you describe here is crying out for a move to C++ and to use an Led class with virtual functions. While that may seem like just function pointers underneath, they are /much/ safer because they are tied so tightly to specific types and uses.

One alternative is to have a "proxy" in the middle that routes between the modules. Another is to have connections handled via a header, perhaps with conditional compilation.

Think of this as analogous to electronics. Function pointers are like free connectors on a board that let you re-wire the board when you use it. That can be very flexible, but makes it very difficult to be sure the board is working and can quickly give you spaghetti systems. C++ virtual functions are like connectors with unique shapes - you can make a few choices of your connections, but only to those points you have specifically allowed. A "proxy" module is like a multiplexer or buffer for controlling the signal routing, and a header with conditional compilation is like DIP switches or jumpers.

Remember, the strength of a programming technique is often best measured in terms of what it /restricts/ you from doing, not from what it /allows/ you to do. It is more important that it is hard to get things wrong, than to make it easy to get things right.

Don't go there. Go to C++, rather than a half-baked home-made solution in C. Using C for OOP like this made sense in the old days - but not now.

That is one drawback. In general, function pointers means you can't make a call-tree from your program. (Embedded systems generally have a "call forest", since interrupts start their own trees, as do threads or tasks.) You can't follow the logic of the program, either with tools or manually.

Reply to
David Brown
[...]

It should be nice to have a few examples of these approaches.

However function pointers usually could assume only a few and fixed values, mostly only *one* value in a build (for example, bsp_led_on() and bsp_led_off() for my previous example). I think that if it's possible to inform the call-graph or stack usage tool of these "connections", it would be possible to generate a good call graph and worst case stack usage.

Another situation where I use function pointers is when I have a callback. Just to describe an example, you have two modules: the lower level module [battery] that monitors continuously the battery level and emit an event (call a function) when the level goes under a custom threshold. Suppose this event must be acquired by [control] module. One solution without function pointers could be:

/* battery.h */ int battery_set_low_thres(uint16_t lvl_mV);

/* battery.c */ #include "battery.h" #include "control.h" .... if (current_lvl < low_thres) { control_battery_low_level_event(current_lvl); } ...

/* control.h */ void control_battery_low_level_event(uint16_t lvl_mV);

/* control.c */ #include "control.h" #include "battery.h"

void control_battery_low_level_event(uint16_t lvl_mV) { bsp_led_on(BSP_LED_BATTERY_LOW); power_down(); }

I don't like it, the lower level module [battery] is stricly coupled to [control] module, because it needs control_battery_low_level_event() function declaration. Indeed "control.h" must be included in battery.c.

Instead I like an approach that uses a function pointer:

/* battery.h */ typedef void (*battery_low_cb)(uint16_t lvl_mV); int battery_set_low_thres(uint16_t lvl_mV);

/* battery.c */ #include "battery.h" .... static battery_low_cb low_cb; .... if (current_lvl < low_thres) { if (low_cb != NULL) low_cb(current_lvl); } ...

/* control.h */ void control_battery_low_level_event(uint16_t lvl_mV);

/* control.c */ #include "control.h" #include "battery.h"

static void control_battery_low_level_event(uint16_t lvl_mV);

void control_init(void) { battery_set_low_thres(2700, control_battery_low_level_event); }

static void control_battery_low_level_event(uint16_t lvl_mV) { bsp_led_on(BSP_LED_BATTERY_LOW); power_down(); }

Here the lower level module [battery] doesn't know anything about the higher level module [control], indeed "control.h" isn't included at all in battery.c.

It appears to me this approach is perfectly legal and much simpler to test because of less linking between modules. Because [battery] doesn't depend on other higher level modules, I can reuse it in other projects as is.

Moreover there are many C projects, even for embedded, that makes large use of this approach. For example, many functions in lwip project accept function pointers to callback (for example, tcp_connect()[1]).

Another approach could be using weak functions:

/* battery.h */ int battery_set_low_thres(uint16_t lvl_mV);

/* battery.c */ #include "battery.h" .... void battery_event_low(uint16_t lvl_mV) __attrinute((weak)); void battery_event_low(uint16_t lvl_mV) { UNUSED(lvl_mV); } .... if (current_lvl < low_thres) { if (low_cb != NULL) battery_event_low(current_lvl); } ...

/* control.h */ void control_battery_low_level_event(uint16_t lvl_mV);

/* control.c */ #include "control.h" #include "battery.h"

void control_init(void) { battery_set_low_thres(2700, control_battery_low_level_event); }

void battery_event_low(uint16_t lvl_mV) { bsp_led_on(BSP_LED_BATTERY_LOW); power_down(); }

[1]
formatting link
Reply to
pozz

The rough idea is that you want to have something like these modules :

blinker (handling timing and the "user interface" of an led) gpio (handling locally connected pins) spi (handling pins connected via an spi bus).

You want the modules to be basically independent. You should be able to write the blinker module without knowing whether the actual led is connected directly to the microcontroller, or via an SPI bus. You should be able to write the gpio and spi modules without knowing what the pins will be used for.

Then you have a "master" module that somehow joins things together.

You have been using function pointers - in "master", you call a function in "blinker" with pointers to functions in "gpio" or "spi".

Alternatively, blinker could call fixed named functions "blink_on" and "blink_off" that are expected to be defined by users of the "blink" module. These would be defined in the "master" module, and call the appropriate functions in "gpio" or "spi". These are the proxy functions, and are a little unusual in that they are declared in "blinker.h", but defined in "master.c".

Another option is to have hooks defined in a header that is included in by modules such as "blinker", and which define the functions to be called for turning the lights on and off. Then the connections are given in that header, not the blinker module. And the blinker module can use conditional compilation - if no hook functions are defined, they are not used.

In both cases, you can use the "blinker", "gpio" and "spi" modules in test harnesses or real code without changing the source code used.

Yes, that's true - but there is no way to express that in C. Even if you use a few specific types (struct typedefs) as parameters to ensure that only functions with very specific signatures are accepted by the compiler, it still means /any/ function with a compatible signature could be used, and your external tools are as helpless for following the code flow.

You can't do that in C.

It is perfectly legal, and may be easier to test - but it is not easier to analyse, and it is harder to follow the code flow. Don't misunderstand me - your use of function pointers here is common and idiomatic. But function pointers have a cost, and alternative structures can be better (though they too have their costs).

Yes, I have made use of weak functions in a similar fashion (noting that weak functions are not standard C). These too will confuse analysis tools.

Reply to
David Brown

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.