1

I'm trying to store different callback function addresses in an array. To be able to store function addresses with different signatures, I have defined a union and I use typecasting. This approach seems to work, but it is not very beautiful. Is there a better solution?

typedef union {
  void (*callback_s_char)(signed char);
  void (*callback_void)(void);
} callback_func_ptr_t;

typedef enum {
    EVENT1,    
    EVENT2,
    MAX_EVENTS  
} event_t;

callback_func_ptr_t callback_func_ptrs[MAX_EVENTS];

void event_func1(signed char direction) {
    printf("success rotated: %d\n", direction);
}
void event_func2(void) {
    printf("success button pressed\n");
}

void register_callback(event_t event, void (*ptr)(void)) {
    callback_func_ptrs[event].callback_void = ptr;
}

int main(void) {

    //register callback functions   
    register_callback(EVENT1, (void (*)(void))event_func1);
    register_callback(EVENT2, (void (*)(void))event_func2);

    //call callbacks
    if(callback_func_ptrs[EVENT1].callback_s_char != NULL)
        (*callback_func_ptrs[EVENT1].callback_s_char)(1);

    if(callback_func_ptrs[EVENT2].callback_void != NULL)
        (*callback_func_ptrs[EVENT2].callback_void)();
}
hat
  • 781
  • 2
  • 14
  • 25
user1160713
  • 81
  • 1
  • 4
  • define union as `typedef union { void* callback; void (*callback_s_char)(signed char); void (*callback_void)(void); } callback_func_ptr_t;` - so add `void* callback;` member. and `void register_callback(event_t event, void* ptr) { callback_func_ptrs[event].callback = ptr; }` and `register_callback(EVENT1, event_func1);` - not need any typecast more – RbMm Jan 31 '18 at 14:40
  • 3
    @RbMm That would be undefined behavior, because `void *` data pointer representation is not guaranteed to be compatible with function pointers. Specifically, this would break on CPUs with Harvard Architecture. – Sergey Kalinichenko Jan 31 '18 at 14:41
  • @dasblinkenlight - this is absolute false. pointer to void and pointer to function - is absolute the same. 4 or 8 bytes depend from system bitness – RbMm Jan 31 '18 at 14:43
  • @RbMm This is a common compiler extension, not a standard. [Relevant Q&A](https://stackoverflow.com/a/13697654/335858). – Sergey Kalinichenko Jan 31 '18 at 14:48
  • @dasblinkenlight - pointer is only the pointer - register size data (i not say about pointer to member in c++ which is different). what i say based not on any standard but my internal knowledge. no matter to what point pointer - to code (function pointer) to data, which is data type. in all case this is simply register size data – RbMm Jan 31 '18 at 14:52
  • 3
    @RbMm *pointer to void and pointer to function - is absolute the same.* Utterly, completely incorrect. The world isn't limited to recent x86 architectures. For example. https://en.wikipedia.org/wiki/Far_pointer Per **6.2.5 Types**, paragraph 27 of [the C standard](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf): "... Pointers to other types need not have the same representation or alignment requirements." – Andrew Henle Jan 31 '18 at 14:53
  • @AndrewHenle - i nothing say about x86. but pointer is register size data. no matter to where it point – RbMm Jan 31 '18 at 14:55
  • 1
    @RbMm: You are true for most common architectures and compilers, and wrong is we speak of C language specifications. Just read the references you were given in comments... – Serge Ballesta Jan 31 '18 at 14:59
  • @SergeBallesta - i say nothing about formal standard. i know that formal by standard this may be incorrect. and also different member of union used - write to one, read from another. but i sure that really is always correct do this on practic. are you can build example when this not true ? – RbMm Jan 31 '18 at 15:02
  • @RbMm In C it is perfectly legal to write to one union member and to read from the other (type punning). In C++ this is prohibited, though. – Sergey Kalinichenko Jan 31 '18 at 15:05
  • @RbMm Do you know what a `far`, `near`, or `huge` pointer are? Those are examples where pointer sizes vary. And given just the x86 architecture, please define "the register size" - then you get to deal with x86's real-mode [segmented addressing](https://en.wikipedia.org/wiki/X86_memory_segmentation). How about architectures with different address sizes for code and data? – Andrew Henle Jan 31 '18 at 15:05
  • 1
    @RbMm: dasblinkenlight has already given an example. [Harvard architecture](https://en.wikipedia.org/wiki/Harvard_architecture) computers use different buses for data and code, so you could have pointers to obj of size 4 and pointers to data of size 8 (or the opposite) – Serge Ballesta Jan 31 '18 at 15:07
  • The code is intended for a harvard architecture (PIC microcontroller with XC8 compiler) – user1160713 Jan 31 '18 at 15:10
  • @user1160713, could you explain what you want to achieve from an array of dissimilar function pointers? You can't _use_ them without additional information, and in the use case you present, it appears that you can simply use a `struct` of correctly-typed function pointers instead of an array. – Toby Speight Jan 31 '18 at 15:25
  • @dasblinkenlight - in c legal, in c++ prohibited - not true by fact. in c++ same as c - also legal. – RbMm Jan 31 '18 at 15:50
  • @AndrewHenle - *what a far, near, or huge pointer are* - are ms-dos like system stile alive ? "the register size" - 4 for x86 code, 8 for x64 for example – RbMm Jan 31 '18 at 15:55
  • 1
    @RbMm *in c legal, in c++ prohibited - not true by fact. in c++ same as c - also legal.* That is one of the scariest statements I've ever noted on Stackoverflow. You're attempting to defend the use code that's undefined behavior because it appears to you that it "works", and worse, you don't even seem to be *trying* to understand why undefined behavior is bad. You've confused "it didn't crash" with "it's legal". That is fundamentally and completely wrong. – Andrew Henle Jan 31 '18 at 16:01
  • @AndrewHenle - the same compiler used for compile both c and c++ code. and by fact will be always and in c and in the c++ - this true. union only shared memory or register – RbMm Jan 31 '18 at 16:03
  • @RbMm *the same compiler used for compile both c and c++ code.* [C and C++ aren't even the same language.](https://www.google.com/search?q=differences+between+C+and+C%2B%2B+site%3Astackoverflow.com) *and by fact will be always and in c and in the c++ - this true. union only shared memory or register* Repeating statements like that just makes it clear that you are not even trying to understand why it's wrong. The majority of computer code out there is not written for x86/Windows and it's not compiled with MSVC. – Andrew Henle Jan 31 '18 at 16:56
  • @AndrewHenle - why you all time repeat x86 ? why not x64 ? – RbMm Jan 31 '18 at 17:01
  • @user1160713: I'm searching for a solution to handle different events through function pointers. A struct would also be possible but I don't see a way of accessing the struct through a register_callback function. Of course it would be possible to directly access the struct from the main function but this is not very beautiful either. – user1160713 Feb 01 '18 at 09:12

1 Answers1

1

Since you must provide the pointer type implicitly at the point of invocation by specifying the correct field of the union, you might as well do it with a cast. Adding a few typedefs and hiding the cast in a macro could make your code more readable:

typedef void (*callback_s_char_t)(signed char);
typedef void (*callback_void_t)(void);

typedef enum {
    EVENT1,    
    EVENT2,
    MAX_EVENTS  
} event_t;

callback_void_t callback_func_ptrs[MAX_EVENTS];

void event_func1(signed char direction) {
    printf("success rotated: %d\n", direction);
}
void event_func2(void) {
    printf("success button pressed\n");
}

void register_callback(event_t event, callback_void_t ptr) {
    callback_func_ptrs[event] = ptr;
}

int main(void) {

    //register callback functions   
    REGISTER_CALLBACK(EVENT1, event_func1);
    REGISTER_CALLBACK(EVENT2, event_func2);

    //call callbacks
    if(callback_func_ptrs[EVENT1])
        GET_CALLBACK(EVENT1,callback_s_char_t)(1);

    if(callback_func_ptrs[EVENT2])
        GET_CALLBACK(EVENT2,callback_void_t)();

    return 0;
}

Demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523