2

I basically want to have a list of functions as an array of function pointers; then I'd like to manage both an index variable into this array, and a pointer variable that would accept values from this array. Consider the following C code illustrating this, which compiles fine:

#include <stdio.h>

void func_01() {
    printf("func_01\n");
}
void func_02() {
    printf("func_02\n");
}
void func_03() {
    printf("func_02\n");
}

enum func_choice_e {
    eFUNC01 = 0,
    eFUNC02 = 1,
    eFUNC03 = 2,
#define _NUM_FUNC_CHOICE 3
    NUM_FUNC_CHOICE = _NUM_FUNC_CHOICE,
};
typedef enum func_choice_e func_choice_t;

void* funcs_ptrs[_NUM_FUNC_CHOICE] = {
    &func_01,
    &func_02,
    &func_03,
};

func_choice_t my_func_choice_idx = eFUNC01;
void* my_func_choice_ptr = &func_01;

int main()
{
    my_func_choice_idx = eFUNC02;
    my_func_choice_ptr = funcs_ptrs[my_func_choice_idx];
    printf("Hello World %p\n", my_func_choice_ptr);

    return 0;
}

As the code above shows, within code I can set the pointer variable through the index variable (my_func_choice_ptr = funcs_ptrs[my_func_choice_idx];).

However, I would like to do something similar, also for the initialization - ultimately, I'd want to just initialize the array values and my_func_choice_idx manually first - and then have the pointer automatically be initialized to the corresponding my_func_choice_idxth element in the array; so instead of the working pointer initializer:

void* my_func_choice_ptr = &func_01;

... I've tried these:

//void* my_func_choice_ptr = funcs_ptrs[my_func_choice_idx]; // error: initializer element is not constant
//void* my_func_choice_ptr = funcs_ptrs[eFUNC01]; // error: initializer element is not constant
//void* my_func_choice_ptr = funcs_ptrs[0]; // error: initializer element is not constant

... and as the error messages in the comments hint, none of these work.

I kind of get why funcs_ptrs[my_func_choice_idx] might fail - my_func_choice_idx is after all a variable -- but by the time the compiler got to my_func_choice_ptr = funcs_ptrs[0];, it should already "know" both the address, size, and initial values of funcs_ptrs -> so I don't see why it cannot set my_func_choice_ptr to the first entry of funcs_ptrs?!

So - is there some syntax I can use for initialization, which will allow me to initialize the pointer variable to a given initial value of an entry in the array (similar to the assignment my_func_choice_ptr = funcs_ptrs[my_func_choice_idx]; which works in normal code)?

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 1
    Does this answer your question? [Error "initializer element is not constant" when trying to initialize variable with const](https://stackoverflow.com/questions/3025050/error-initializer-element-is-not-constant-when-trying-to-initialize-variable-w) – derpirscher Aug 20 '23 at 11:10
  • Thanks @derpirscher - no that did not quite answer this question; the accepted answer in here does. – sdbbs Aug 20 '23 at 11:19

2 Answers2

2

You are declaring the function pointers wrong. Function pointers are not void* but need to have the proper signature:

typedef void(*func_ptr)(void);

which you then can use like so:

const func_ptr funcs_ptrs[_NUM_FUNC_CHOICE] = {
    &func_01,
    &func_02,
    &func_03,
};

func_choice_t my_func_choice_idx = eFUNC01;
func_ptr my_func_choice_ptr = funcs_ptrs[eFUNC01];

If you want to initialize it using my_func_choice_idx, you can't do it globally since it's not a constant, but you can do it in main:

int main(void) {
    func_ptr my_func_choice_ptr = funcs_ptrs[my_func_choice_idx];
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Thanks @TedLyngmo - I had forgotten about proper function signatures, you're absolutely right about that. However, your example still has `my_func_choice_ptr = &func_01;` (i.e. initializing pointer variable directly from a given function, instead of through the pointers already stored in `funcs_ptrs`), so in that sense it does not answer my question as stated. – sdbbs Aug 20 '23 at 11:04
  • 1
    @sdbbs You're welcome! I updated the answer. – Ted Lyngmo Aug 20 '23 at 11:11
  • Many thanks @TedLyngmo for the edit - that was it! I was more less aware that re: `my_func_choice_idx`, I "can't do it globally since it's not a constant" - but I was wondering why cannot I initialize via say `funcs_ptrs[eFUNC01];` ... and your example finally made me see it working as an initializer for the first time: using the proper function signature via the `func_ptr` typedef, is what made `funcs_ptrs[eFUNC01];` compile as an initializer for me. – sdbbs Aug 20 '23 at 11:17
  • 1
    @sdbbs Great that it worked out! You are welcome! – Ted Lyngmo Aug 20 '23 at 11:18
0

void* is the generic object pointer type, which can be used when dealing with pointers to objects, that is: pointers to variables (or pointers to other pointers).

No such type exists for function pointers and void* is not compatible with function pointers.

The overwhelmingly strong recommendation for best practices when dealing with function pointers is to always use a typedef. Two "schools"/styles exist:

typedef void (*func_t) (void);  // define a function pointer type
...
func_t fptr; // function pointer

and

typedef void func_t (void); // define a function type
...
func_t* fptr; // function pointer

I would recommend the second style since that's more readable and makes function pointer use consistent with object pointers. Generally it is also bad practice to hide pointers behind typedef. Also it makes it easier to separate "constant pointer" from "pointer to constant" if we don't hide the pointer under a typedef.


Other things/misc code review:

  • void func_01() is an old style format not recommended for now. It usually does not matter except when dealing with function pointers - the correct format is void func_01(void). Otherwise an empty parenthesis means "function accepting any parameter", which is not what you want (and notably C and C++ are different here). Upcoming C23 will finally fix this, but for older/existing code bases always use explicit (void).

  • The enum is fishy. First of all, if you do not assign a value to enumeration constants then they are guaranteed to be enumerated 0,1,2,3 etc. When you do assign a value, the next constant in the list gets implicitly set to the previous one + 1. And when using the trick to count the number of available enumeration constants in a list of 0,1,2,3... we use this to let the final item in the enum get automatically assigned, thereby representing the number of enum items available. To let the compiler automatically assign values makes the code more maintainable.

    Also the typedef enum can be simplified but that's a style remark. Similarly, enum tags aren't very useful most of the time.

  • When declaring an array of function pointers corresponding to enum items, we can use that one last item in the enum list to check for integrity between the array and the enum, ensuring that they have a 1-to-1 correspondence. The recommended way is to use a _Static_assert.

  • Function pointer arrays should almost certainly be made read-only, unless you have very good reasons to assign new values to them in run-time. Not usually the case.

  • A little hint: inside a function you can use the C feature __func__ to get the name of the function as a string literal. Therefore printf("func_01\n"); can be rewritten as puts(__func__);, to reduce code repetition a tiny bit.

  • %p isn't actually well-defined for function pointers. If you want to print their addresses, it is better to convert to uintptr_t first.


After fixing all of the above, the code might look like this instead:

#include <stdio.h>
#include <stdint.h>

void func_01(void) {
    puts(__func__);
}
void func_02(void) {
    puts(__func__);
}
void func_03(void) {
    puts(__func__);
}

typedef enum  {
    eFUNC01,
    eFUNC02,
    eFUNC03,
    NUM_FUNC_CHOICE,
} func_choice_t;

typedef void func_t (void);

// note const, the pointers themselves are read-only:
func_t* const funcs_ptrs[] = { // leave size blank here
    &func_01,
    &func_02,
    &func_03,
};

// ensure integrity of enum vs array:
_Static_assert(sizeof(funcs_ptrs)/sizeof *funcs_ptrs == NUM_FUNC_CHOICE,
               "func_choice_t doesn't match funcs_ptrs");


int main (void)
{
    func_choice_t my_func_choice_idx = eFUNC01;
    func_t* my_func_choice_ptr = &func_01;

    my_func_choice_idx = eFUNC02;
    my_func_choice_ptr = funcs_ptrs[my_func_choice_idx];
    printf("Hello World %lu\n", (uintptr_t)my_func_choice_ptr);
    my_func_choice_ptr(); // should print func_02

    return 0;
}
Lundin
  • 195,001
  • 40
  • 254
  • 396