0

Edit: I tried a simple code in Arduino IDE which should've done in the first place instead in doing it in a desktop compiler. Because the results are different by experiment. In the desktop C compiler, it's working and bypassing the code without much problems, just one or two warnings.

But in Arduino IDE, it's not acceptable and gives error.

This is the code:

// functions
void function1(uint8_t x, uint8_t y){
  int z = x + y;
  Serial.println("function1\n");
  Serial.println(z);
}
uint8_t function2(uint8_t x, uint8_t y){
    Serial.println("function2\n");
    return 1;
}

void (*fun_ptr)();

void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(9600);
  fun_ptr = (void (*)())function2;
  int value;
  
  value = fun_ptr(1,2);

}

void loop() {
  // put your main code here, to run repeatedly:

}
8
  • 1
    Of course you get that warning, since fun_ptr is a pointer to a function without any parameters, while fun1() is a function with a uint8_t parameter. Though it is unclear to me, why you are doing this, since you want to implement your FSM with a switch case. What structure do you want to build with the function pointer? Please show it the the context of your FSM, then we can try give you a good answer
    – chrisl
    Commented Jan 10, 2021 at 14:55
  • I edited the post.
    – R1S8K
    Commented Jan 10, 2021 at 15:38
  • 1
    Note that the only place you call fun_ptr(), you do not supply any argument. Thus it wouldn't even make sense for fun_ptr to reference a function that does expect arguments. Commented Jan 10, 2021 at 15:48
  • OK, I'm trying to fix the code for now and thanks for the note.
    – R1S8K
    Commented Jan 10, 2021 at 16:13
  • 1
    the auto type, templates/macros, and "void pointers" can be your best friends in this case
    – dandavis
    Commented Jan 12, 2021 at 4:25

3 Answers 3

1

It looks to me like you are trying to find the right answer to the wrong question, typical of an XY problem. In the example you provide in your own answer, you call the function pointer right after having set its value. In this case, there is no use to the function pointer, as you could directly call either read_chars() or write_chars().

Obviously, this is just because that example is excessively simplified. In a more realistic use case, you would set the function pointer in one part of the code, and call it in a completely different part. Assuming the functions and the pointer are defined like this:

void (*fun_ptr)();

void read_chars(uint8_t address) {
    printf("address of array is 0x%.2x\n", address);
}

void write_chars(uint8_t row, uint8_t col, const char *str) {
    printf("[%d, %d]: %s\n", row, col, str);
}

One section of the program sets the pointer:

// Here we learn what action should be performed.
if (should_read()) {
    fun_ptr = (void(*)()) read_chars;
} else {  // should write
    fun_ptr = (void(*)()) write_chars;
}

and a different section calls it:

// Here we have to perform the action.
if (fun_ptr == (void(*)()) read_chars) {
    ((void(*)(uint8_t)) fun_ptr)(1);
} else if (fun_ptr == (void(*)()) write_chars) {
    ((void(*)(uint8_t, uint8_t, const char *)) fun_ptr)(
            1, 1, "messaage0");
}

Note that this second section must know what function it is calling in order to pass the correct arguments. Hence the if...then test. Note also that, in order to have valid C++, you must explicitly cast the function pointer before calling it. This gets very clumsy, and it is a lot more convenient to store this information (what function should be called) in an enum rather than a function pointer:

enum {
    CALL_NONE, CALL_READ_CHARS, CALL_WRITE_CHARS
} what_to_call = CALL_NONE;

which would be used like this:

// Here we learn what action should be performed.
if (should_read()) {
    what_to_call = CALL_READ_CHARS;
} else {
    what_to_call = CALL_WRITE_CHARS;
}

// ...

// Here we have to perform the action.
if (what_to_call == CALL_READ_CHARS)
    read_chars(1);
else if (what_to_call == CALL_WRITE_CHARS)
    write_chars(1, 1, "messaage1");

That being said, it is often the case that when it is time to perform the action, the part of the code responsible of doing so does not know what arguments should be passed, and this information is more readily available at the time when when learn what action should be performed. This is a quite common situation, and a standard C idiom in this case is to give the function a void * parameter that it will cast to the appropriate type in order to retrieve the data it needs. For instance:

void (*fun_ptr)(void *);
void *fun_ptr_data;

void read_chars(void * data) {
    uint8_t address = * (uint8_t *) data;
    printf("address of array is 0x%.2x\n", address);
}

struct write_chars_data {
    uint8_t row;
    uint8_t col;
    const char *str;
};

void write_chars(void *data) {
    write_chars_data *d = (write_chars_data *) data;
    printf("[%d, %d]: %s\n", d->row, d->col, d->str);
}

Note that, since write_chars() needs to retrieve multiple parameters from a single pointer, it uses a pointer to a struct.

Now, the part of the code that learns what should be done has the responsibility of providing the data:

// Here we learn what action should be performed.
if (should_read()) {
    fun_ptr = read_chars;
    uint8_t *p = new uint8_t;
    *p = 1;
    fun_ptr_data = (void *) p;
} else {  // should write
    fun_ptr = write_chars;
    write_chars_data *p = new write_chars_data;
    p->row = 1;
    p->col = 1;
    p->str = "messaage2";
    fun_ptr_data = (void *) p;
}

The part of the code that has to perform the action now is very simple:

// Here we have to perform the action.
if (fun_ptr)
    fun_ptr(fun_ptr_data);

Note that this code snippet doesn't free the allocated memory. You will have to figure out who will be responsible for that.

This pattern is quite common in libraries, where the client (the user of the library) wants an action to be performed, but the library is responsible for performing that action at the right time. The client would then provide the library a pointer to a function that accomplishes the action (called a “callback”), together with a generic pointer to the data needed by the callback. The use of a generic void * pointer makes sense, because the library cannot know what kind of data the callback will need.

In your case, since you know what kind of data you may need, you could use a pointer to an union instead of a generic pointer:

union fun_data {
    uint8_t address;  // for read_chars
    struct {          // for write_chars
        uint8_t row;
        uint8_t col;
        const char *str;
    };
};

void (*fun_ptr)(fun_data *);
fun_data *fun_ptr_data;

A more idiomatic C++ style would be to pack together the callback function and its data into a “functor”, i.e. an object that can be called like a function, and can also store the data it needs. You would then use inheritance in order to create specific functor types while calling a generic one.

1
  • 1
    @R1S8K: 1. You can have the callbacks return a number if you want, but that has to be done consistently: all the callbacks should return the same type, and the pointer should be consistent with their signature. You should not need to typecast when assigning or using the function pointer. 2. The issue with your answer is not that it doesn't work. It is that it is fragile and bad practice. Being illegal C++, it can very well work on one platform/compiler combination, and fail on the next one. Commented Jan 23, 2021 at 11:02
2

I assume, that the fun_ptr points to the different service functions to do some kind of animation other other long running tasks on your display, thus you want to repeatedly call them in a structured way.

What you are currently doing will not really work. When you call a function, you need to provide its arguments. That's the same for a function pointer. You cannot call a function, that demands parameters, without any parameters. While you can still set a function pointer of type (void (*)(void*) to a function of type (void (*)(uint8_t)), the compiler warns so, because you still cannot call the function without any parameter. If you are trying to do so, you will still get an error of too few arguments to function ....

I see 2 ways to go from here:

  • You could overload your functions, so that a version without any parameters exists. Like this:

      void foo(uint8_t i){Serial.println(i);}
      void foo(){foo(10);}
    

    With 10 being the value, with which the function should be called, when you provide no parameters. You can of course also use variables or function calls here, to actually get that value for the parameter i. Then you can use the pointer to call that function:

      void (* p)(void);
      p = &foo;
      p();
    

    The assignment of the pointer will choose the version of the function without any parameters out of the overloaded pool of this function, since that matches the type of the pointer. Note, that I removed the * from the void parameter definition. I don't understand, what that would mean anyway (pointer to void?). The function pointer p() can now be called without any parameters, since it points to a function without any parameters.

  • You could ditch the function pointer and use a simple switch case statement with a state variable (which holds some identifier for the corresponding function), to call the correct service function. I won't go into detail here, since that is already basically, how an FSM can be programmed in C++, which is covered on this site and on the web numerous times.

If I were you, I probably would go with the first option, though that might also depend on the overall logic, that I currently don't really understand (too few information).

1
  • Re “pointer to void?”: A void * is commonly used as a generic pointer, i.e. a pointer to a data item of unknown type. See for example the interface of qsort(). Commented Jan 10, 2021 at 19:27
0

Here's my latest work around:

I was searching for a work around. I got back to copy a method of casting a function pointer.

And changed my data structure of having 2 nested structs to one that contain everything which should serve my project well.

Here's my work:

task_manager.h

///////////////////////////////////////////////////////////////////////////
// enumerations
typedef enum {NOT_FINISHED,FINISHED,DONE}STATE;
// variables
typedef void (*f_ptr)(void *args);

// structs
typedef struct __attribute__((__packed__)) THREAD{
    uint8_t tsk_cnts, tsk_cntr;     // task information
    STATE   thrd_st;                  // thread flag states
    f_ptr   *tsk_fptr;
}THREAD;

main.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "task_manager.h"

const unsigned char PIC[] = {9, 8, 7, 1, 2};

void function1(void){printf("function1\n");}
void function2(uint8_t i){printf("function2\nnumber = %d\n",i);}
void function3(uint8_t a, uint8_t b, const uint8_t *img){
    printf("function3\na#%d b#%d\narray[0] = %d\n",a,b,*(img+0));}
uint8_t function4(uint8_t i){printf("function4# return\n"); return i+=1;}

THREAD thread1;

uint8_t fn4_ret;

int main(){

    // set thread main info
    thread1.thrd_st = 0;
    thread1.tsk_cnts = 4;
    thread1.tsk_cntr = 0;

    // allocating memory for tasks
    thread1.tsk_fptr = malloc(4*sizeof(f_ptr));

    // store tasks addresses
    thread1.tsk_fptr[0] = (void(*)())function1;
    thread1.tsk_fptr[1] = (void(*)())function2;
    thread1.tsk_fptr[2] = (void(*)())function3;
    thread1.tsk_fptr[3] = (void(*)())function4;

    // call the functions
    ((void(*)())thread1.tsk_fptr[1])(9);
    ((void(*)())thread1.tsk_fptr[2])(1,2,PIC);
    fn4_ret = ((uint8_t(*)())thread1.tsk_fptr[3])(1);
    printf("\fn4_ret = %d\n",fn4_ret);


    return 0;
}

It compiled with me fine, passed what I want and returned the required data.

2
  • 1
    Even though that may work on some compiler/platform, it is illegal in C++. gcc says about the cast: error: invalid conversion from ‘void (*)()’ to ‘uint8_t (*)()’, and about the call through the pointer: error: too many arguments to function. Commented Jan 16, 2021 at 11:23
  • I edited my answer. What you think ? It worked with me well. No problems.
    – R1S8K
    Commented Mar 31, 2021 at 16:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.