1

I am going through a code implementation of USB drivers on an STM32 MCU. My C language understanding is a bit limited and I have come across this function definition that is not clear to me.

static enum usbd_request_return_codes cdcacm_control_request(
usbd_device *usbd_dev __attribute__((unused)),
struct usb_setup_data *req,
uint8_t **buf __attribute__((unused)),
uint16_t *len,
void (**complete)
    (
        usbd_device *usbd_dev,
        struct usb_setup_data *req
    ) __attribute__((unused))
)

I don't understand the last argument in the function declaration where it seems like it is actually defining another function for the argument and takes weird two asterisks as parameter. Could somebody explain what is this and how it might be used in the actual function call?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
A. Munir
  • 125
  • 4
  • 2
    `complete` is a pointer to a pointer to a function. Please do some reding about *function pointers*. Using pointers to pointers is a common way to emulate *pass by reference* in C. – Some programmer dude Sep 08 '20 at 10:35
  • It's just yet another crappily-written API where they use function pointers without typedef. Some horrible Github barf project, am I right... – Lundin Sep 08 '20 at 11:00
  • @Lundin I agree that a `typedef` should be used here. However, the project is the excellent [libopencm3](https://github.com/libopencm3/libopencm3), an open-source project for ARM micro controllers. In particular, the USB implementation is superior to other comparable frameworks. – Codo Sep 08 '20 at 11:14
  • @Codo I took a brief look at it and the source is unimpressive; at a glance (at the CAN driver) I found several questionable tricks and non-portable constructs. – Lundin Sep 08 '20 at 13:01
  • Clearer: `complete` is a pointer to: a pointer to a function. – Danijel Sep 08 '20 at 13:34

1 Answers1

2

This

void (**complete)
    (
        usbd_device *usbd_dev,
        struct usb_setup_data *req
    ) __attribute__((unused))

is a declaration of a pointer to function pointer. The function has the return type void and two parameters.

To make it more clear consider the demonstrative program below.

#include <stdio.h>

void f( int x, int y )
{
    printf( "x + y = %lld\n", ( long long int )x + y );
}

void g( int x, int y, void ( **fp )( int, int ) )
{
    ( *fp )( x, y );
}

int main(void) 
{
    void ( *fp )( int, int ) = f;
    
    g( 10, 20, &fp );
    
    return 0;
}

The program output is

x + y = 30

Without a more broad context it is difficult to say why a pointer to pointer to function is used. Maybe it is due to that the parameter is an output parameter. Or there can be a dynamically allocated array of pointers to functions.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Doesn't explain why they'd pass the function pointer by pointer though. The only reason you'd do that is if you expect to return a function pointer through a parameter (which is in turn API smell). – Lundin Sep 08 '20 at 11:04
  • Was it necessary to put parenthesis around **complete or was it just developer decision? – A. Munir Sep 08 '20 at 14:19
  • @A.Munir It is required. Otherwise the function will have the return type void **. – Vlad from Moscow Sep 08 '20 at 14:20
  • @VladfromMoscow thank you so much for your answer and accompanying example. – A. Munir Sep 08 '20 at 14:34
  • The reason it is a pointer to a pointer is: it's an *out* parameter. So with the *complete* parameter, you can return a function pointer. The returned function will be called later when the control request completes. The original code is from a libopencm3 example. In the mean time, a typedef has been introduced: `usbd_control_complete_callback`. It could be used to simplify the code. The parameter is not used in your case. That's why `__attribute__((unused))` is appended. – Codo Sep 09 '20 at 12:40