4

So, I have a huge set of functions to read inputs from some source of the form:

ErrCode_t in_func1(t1_t * const data1);
ErrCode_t in_func2(t2_t * const data2);
...

some trigger functions, telling me if I may call the functions above:

TrCode_t tr_func1();
TrCode_t tr_func2();
...

and corresponding functions to write out the data:

void out_func1(t1_t const * const data1, const uint32_t handled_error);
void out_func2(t2_t const * const data2, const uint32_t handled_error);
...

Also there is a quite complicated algorithm depending on the trigger functions, that decides if I may call the input function, or not. (this is a simplified picture; their more then one trigger function and timers envolved for each I/O).

This algo but basicly says: If the trigger says yes, call the input function with a pointer to a data variable, check the error, do some validation, and then pass the pointer of the updated variable to the output.

 void execute_f1(t1_t * const pData1)
 {
    if(evalTr(tr_func1()))
    {
      const ErrCode_t myErr = in_func1(pData1);
      const uint32_t myOutErr = evalError(myErr);

      out_func1(pData1,myOutErr);
    }
 }

(while evalTr and evalError shall be some evaluation functions that are used correctly)

I would like to encapsulate this algo in a own function,

 void execute_io(???)

to be called with some function pointers to perform this. But I can't think of a pattern that would be conform to the standard, without a huge amount of wrapper functions. I wrap the input-functions and output functions to perfom the correct casts, and to adjust the signatures like:

ErrCode_t my_in_func1(void * const pData1)
{
    t1_t * const data1 = (t1_t*) pData1;
    return in_func1(data1);
}

and output functions alike:

 void my out_func2(void const * const data2, const uint32_t handled_error) {...}

so that I have homogeneous signatures, and that way easy function pointers. But I would really prefer not to wrap all those functions. Do anyone know a pattern that would work "inside" execute_io and surrounding code, so I don't have to wrap all that functions?

Update: And here in a combination as requested from barak manos:

system_interface.h

 ErrCode_t in_func1(t1_t * const data1);
 /* some 500 more of them */

 TrCode_t tr_func1();
 /* some 500 more of them */

 void out_func1(t1_t const * const data1, const uint32_t handled_error);
 /* some 500 more of them */

my_code.c

 static ErrCode_t my_in_func1(void * const data1)
 {
    t1_t * const data1 = (t1_t*) pData1;
    return in_func1(data1);
 }
 /* and some 500 more wrappers */

 static void my_out_func1(void const * const pData1, const uint32_t handled_error)
 {
    t1_t const * const data1 = (t1_t) pData1;
    out_func1(pData1, handled_error);
    return;
 }
 /* and some 500 more wrappers */

typedef ErrCode_t (*inFuncPtr)(void * const);
typedef void (*outFuncPtr)(void const * const, const uint32_t);
typedef TrCode_t (*trFuncPtr)();

execute_io(inFuncPtr inFunc, outFuncPtr outFunc, trFuncPtr trFunc, void * pData)
{
   if(evalTr((*trFunc)()))
   {
      const ErrCode_t myErr = (*inFunc)(pData);
      const uint32_t myOutErr = evalError(myErr);

      (*outFunc)(pData,myOutErr);
   }
   return;
}

void do_all_my_work()
{
   {
     t1_t data1;
     execute_io(&my_in_func1, &my_out_func1, &tr_func1, &data1);
   }
   {
     t2_t data2;
     execute_io(&my_in_func2, &my_out_func2, &tr_func2, &data2);
   }
   /* and some 499 other calls */
}

I want to find another pattern, that does not force me to wrap all that I/O functions. (and no, the above code is surely not an executable example but merely a concept)

2
  • Can you please just show the complete piece of code that you currently have, and point out what exactly you are not happy with? It would make it a lot easier for us than trying to guess how all these code snippets are combined together into that. Commented May 13, 2015 at 18:37
  • @barakmanos hope, the update helps. Commented May 13, 2015 at 20:00

1 Answer 1

0

I wrote something that I believe does what you want. Let me know if I'm missing something. I wrote stubs for the various functions and types that you mentioned just so the code would compile, but it should work no matter the types used or the implementation used in the functions you defined. Basically it works because all pointers are just integers that give the address of a value in memory, and the contents of that memory are unimportant for passing the pointer as long as the pointer points to a location that is sufficiently large to contain the value needed. The only issue that can sometimes throw a wrench into this notion is that some compilers require specific data types to be aligned along specific byte boundaries. To get around this we first notice that these byte boundaries are all powers of 2 and that the alignment will never exceed the size of the largest primitive which for c is currently 64 bits (8 bytes). This is true even for non-primitive types eg. struct/union. Then all we have to do is allocate an additional 8 bytes in memory on the buffer that needs to hold this typed data, and then add a value between 0 and 7 to the pointer to make it divisible by 8 and thus eliminating the alignment problem on all machines that support 64 bit pointers or less.

    /*
 * Test.c
 *
 *  Created on: May 14, 2015
 *      Author: tiger
 */
#include <stdio.h>

typedef int ErrCode_t;
typedef int TrCode_t;
typedef unsigned int uint32_t;
typedef unsigned char uint8_t;
typedef unsigned long long int uint64_t;

typedef int t1_t;
typedef short t2_t;
typedef float t3_t;
typedef double t4_t;
typedef long long int t5_t;

#define MAX_DATA_TYPE_SIZE ((uint32_t)64) //or whatever the size is of your largest data type

uint32_t evalTr(TrCode_t code);
uint32_t evalError(ErrCode_t code);

ErrCode_t in_func1(t1_t* const data1);
ErrCode_t in_func2(t2_t* const data2);
ErrCode_t in_func3(t3_t* const data3);
ErrCode_t in_func4(t4_t* const data4);
ErrCode_t in_func5(t5_t* const data5);

TrCode_t tr_func1();
TrCode_t tr_func2();
TrCode_t tr_func3();
TrCode_t tr_func4();
TrCode_t tr_func5();

void out_func1(t1_t const* const data1, const uint32_t handled_error);
void out_func2(t2_t const* const data2, const uint32_t handled_error);
void out_func3(t3_t const* const data3, const uint32_t handled_error);
void out_func4(t4_t const* const data4, const uint32_t handled_error);
void out_func5(t5_t const* const data5, const uint32_t handled_error);

typedef struct
{
    TrCode_t (*tr_func)(void);
    ErrCode_t (*in_func)(void* const data);
    void (*out_func)(const void* const data, const uint32_t handled_error);
}IOSet;

#define FUNCTION_COUNT ((uint32_t)5)
IOSet ioMap[FUNCTION_COUNT] =
{
    {.tr_func = (void*)&tr_func1, .in_func = (void*)&in_func1, .out_func = (void*)&out_func1},
    {.tr_func = (void*)&tr_func2, .in_func = (void*)&in_func2, .out_func = (void*)&out_func2},
    {.tr_func = (void*)&tr_func3, .in_func = (void*)&in_func3, .out_func = (void*)&out_func3},
    {.tr_func = (void*)&tr_func4, .in_func = (void*)&in_func4, .out_func = (void*)&out_func4},
    {.tr_func = (void*)&tr_func5, .in_func = (void*)&in_func5, .out_func = (void*)&out_func5}
};

void execute_io(IOSet io, void * const pData)
{
    if(evalTr(io.tr_func()))
    {
        const ErrCode_t myErr = io.in_func(pData);
        const uint32_t myOutErr = evalError(myErr);

        io.out_func(pData,myOutErr);
    }
}

void do_all_my_work()
{
    uint32_t i;
    //allocate a buffer sufficiently large to hold any of the data types on the stack
    // + 8 to allow correcting pointer for any alignment issues
    uint8_t dataBuffer[MAX_DATA_TYPE_SIZE + 8];
    uint64_t dataHandle = (uint64_t)&dataBuffer;
    //ensure the dataHandle is divisible by 8 to satisfy all possible alignments
    //all 8 byte alignments are also valid 4 byte alignments
    //all 4 byte alignments are also valid 2 byte alignments
    //all 2 byte alignments are also valid 1 byte alignments

    //you can use a smaller type than uint64_t for this computation if your pointers are smaller,
    //but this should work for pointers of all sizes up to 64 bits
    if((dataHandle % 8) > 0)
    {
        dataHandle += 8ULL-(dataHandle % 8);
    }

    for(i = 0; i < FUNCTION_COUNT; i++)
    {
        execute_io(ioMap[i], (void*)dataHandle);
    }
}

uint32_t evalTr(TrCode_t code)
{
    static uint32_t result = 0;
    result ^= 1;
    return result;
}

uint32_t evalError(ErrCode_t code)
{
    return 0;
}

ErrCode_t in_func1(t1_t* const data1)
{
    *data1 = 1;
    return 0;
}

ErrCode_t in_func2(t2_t* const data2)
{
    *data2 = 2;
    return 0;
}

ErrCode_t in_func3(t3_t* const data3)
{
    *data3 = 3;
    return 0;
}

ErrCode_t in_func4(t4_t* const data4)
{
    *data4 = 4;
    return 0;
}

ErrCode_t in_func5(t5_t* const data5)
{
    *data5 = 5;
    return 0;
}


TrCode_t tr_func1()
{
    return 0;
}

TrCode_t tr_func2()
{
    return 0;
}

TrCode_t tr_func3()
{
    return 0;
}

TrCode_t tr_func4()
{
    return 0;
}

TrCode_t tr_func5()
{
    return 0;
}


void out_func1(t1_t const* const data1, const uint32_t handled_error)
{
    printf("%d\n", *data1);
    return;
}

void out_func2(t2_t const* const data2, const uint32_t handled_error)
{
    printf("%d\n", *data2);
    return;
}

void out_func3(t3_t const* const data3, const uint32_t handled_error)
{
    printf("%f\n", *data3);
    return;
}

void out_func4(t4_t const* const data4, const uint32_t handled_error)
{
    printf("%f\n", *data4);
    return;
}

void out_func5(t5_t const* const data5, const uint32_t handled_error)
{
    printf("%llu\n", *data5);
    return;
}

int main()
{
    for(;;)
    {
        do_all_my_work();
    }
    return 0;
}
Sign up to request clarification or add additional context in comments.

12 Comments

I have to walk true your solution for a little to understand your solution. But my first question would be: Is it safe to cast a function pointer to void * as you did in the IOMap table? (are function pointer guaranteed fit in a void *. I thought this guarantee only holds for object pointer)
all pointers in a system are the same size, the only size that changes is the size of the value that they point to. Now the alignment of void* is only guarenteed to be the same as char*, however, i convert the function pointer to a void* and then assign that void* to a preallocated function pointer so the pointer that i invoke as a function is guarenteed to have the correct alignment and value as a function pointer
And second question: is it save to dereference a function pointer declared to have different signature, than the function pointed to? That happens when you use oi.in_func and oi.out_func as the data pointer of the function pointed to is not of void * but of tx_t.
And regarding the pointer size of function and object pointer are the same size (and compatible with void * casts): Is this "just" reality, or also the case according to the C standards? Besides that, the solution really looks good.
again c doesn't actually care about the type of your variables, only the size and alignment of variables. so as long as the arguments i pass have the same size and alignments as the original function it doesnt matter if the superficial type is different
|

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.